Compare commits

...

8 Commits

Author SHA1 Message Date
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
11 changed files with 89 additions and 85 deletions

View File

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

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 6.4.4
// @version 6.4.6
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@ -192,7 +192,7 @@ class UserAgent {
});
}
}
var SCRIPT_VERSION = "6.4.4", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface;
var SCRIPT_VERSION = "6.4.6", 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,
@ -382,6 +382,7 @@ class GhPagesUtils {
var SUPPORTED_LANGUAGES = {
"en-US": "English (US)",
"ca-CA": "Català",
"cs-CZ": "čeština",
"da-DK": "dansk",
"de-DE": "Deutsch",
"en-ID": "Bahasa Indonesia",
@ -571,6 +572,7 @@ var SUPPORTED_LANGUAGES = {
"new-version-available": [
e => `Version ${e.version} available`,
e => `Versió ${e.version} disponible`,
e => `Verze ${e.version} dostupná`,
,
e => `Version ${e.version} verfügbar`,
e => `Versi ${e.version} tersedia`,
@ -616,6 +618,7 @@ var SUPPORTED_LANGUAGES = {
"press-key-to-toggle-mkb": [
e => `Press ${e.key} to toggle this feature`,
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 => `${e.key}: Funktion an-/ausschalten`,
e => `Tekan ${e.key} untuk mengaktifkan fitur ini`,
@ -641,6 +644,7 @@ var SUPPORTED_LANGUAGES = {
e => `Recommended settings for ${e.device}`,
e => `Configuració recomanada per a ${e.device}`,
,
,
e => `Empfohlene Einstellungen für ${e.device}`,
e => `Rekomendasi pengaturan untuk ${e.device}`,
e => `Ajustes recomendados para ${e.device}`,
@ -751,6 +755,7 @@ var SUPPORTED_LANGUAGES = {
"touch-control-layout-by": [
e => `Touch control layout by ${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-Steuerungslayout von ${e.name}`,
e => `Tata letak Sentuhan layar oleh ${e.name}`,
@ -2688,11 +2693,12 @@ function setPref(prefKey, value, origin) {
}
function checkForUpdate() {
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);
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.latest", json.tag_name.substring(1), "direct"), setGlobalPref("version.current", SCRIPT_VERSION, "direct");
}), Translations.updateTranslations(currentVersion === SCRIPT_VERSION);
setGlobalPref("version.lastCheck", now, "direct"), Translations.updateTranslations(currentVersion === SCRIPT_VERSION);
}
function disablePwa() {
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) {
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;
}
let layoutChanged = TouchController.#currentLayoutId !== layoutId;
@ -5133,11 +5139,6 @@ var LOG_TAG2 = "Patcher", PATCHES = {
if (index < 0 || PatcherUtils.indexOf(str, '"AppInsightsCore', index, 200) < 0) return !1;
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) {
let text = "this.enableLightweightTelemetry=!";
if (!str.includes(text)) return !1;
@ -5165,10 +5166,10 @@ var LOG_TAG2 = "Patcher", PATCHES = {
let layout = getGlobalPref("ui.layout") === "tv" ? "tv" : "default";
return str.replace(text, `?"${layout}":"${layout}"`);
},
remotePlayDirectConnectUrl(str) {
let index = str.indexOf("/direct-connect");
if (index < 0) return !1;
return str.replace(str.substring(index - 9, index + 15), "https://www.xbox.com/play");
remotePlayPostStreamRedirectUrl(str) {
let text = ".RemotePlayRoot.getLink()):";
if (!str.includes(text)) return !1;
return str = str.replace(text, ".Home.getLink()):"), str;
},
remotePlayKeepAlive(str) {
let text = "onServerDisconnectMessage(e){";
@ -5212,14 +5213,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;
},
enableXcloudLogger(str) {
let text = "this.telemetryProvider=e}log(e,t,r){";
if (!str.includes(text)) return !1;
let index = str.indexOf("this.telemetryProvider.trackErrorLike");
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 = `
const [logTag, logLevel, logMessage] = Array.from(arguments);
const logFunc = [console.debug, console.log, console.warn, console.error][logLevel];
logFunc(logTag, '//', logMessage);
`;
return str = str.replaceAll(text, text + newCode), str;
return str = PatcherUtils.insertAt(str, index, newCode), str;
},
enableConsoleLogging(str) {
let text = "static isConsoleLoggingAllowed(){";
@ -5548,11 +5549,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;
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) {
let text = "=window.__PRELOADED_STATE__;";
if (!str.includes(text)) return !1;
@ -5669,7 +5665,6 @@ ${subsVar} = subs;
"broadcastPollingMode",
getGlobalPref("ui.gameCard.waitTime.show") && "patchSetCurrentFocus",
"patchGamepadPolling",
"optimizeGameSlugGenerator",
"modifyPreloadedState",
"detectBrowserRouterReady",
"exposeStreamSession",
@ -5699,13 +5694,11 @@ ${subsVar} = subs;
] : [],
...getGlobalPref("block.tracking") ? [
"disableAiTrack",
"disableTelemetry",
"blockWebRtcStatsCollector",
"disableIndexDbLogging",
"disableTelemetryProvider"
] : [],
...getGlobalPref("xhome.enabled") ? [
"remotePlayDirectConnectUrl",
"remotePlayKeepAlive",
"remotePlayDisableAchievementToast",
STATES.userAgent.capabilities.touch && "patchUpdateInputConfigurationAsync"
@ -5748,6 +5741,7 @@ ${subsVar} = subs;
"patchPollGamepads",
getGlobalPref("stream.video.combineAudio") && "streamCombineSources",
...getGlobalPref("xhome.enabled") ? [
"remotePlayPostStreamRedirectUrl",
"patchRemotePlayMkb",
"remotePlayConnectMode"
] : [],
@ -8502,6 +8496,9 @@ class RemotePlayManager {
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();
@ -8585,7 +8582,7 @@ class XhomeInterceptor {
if (hasTouchSupport) TouchController.disable(), BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {
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;
}
static async handleTitles(request) {
@ -9083,9 +9080,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 = [];
for (let item2 of candidates) {
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);
}
}
if (options.preferIpv6Server) lst.sort((a, b) => {
let firstIp = a.ip, secondIp = b.ip;
return !firstIp.includes(":") && secondIp.includes(":") ? 1 : -1;
@ -9568,7 +9568,7 @@ function patchVideoApi() {
return nativePlay.apply(this);
}
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);
};
}

File diff suppressed because one or more lines are too long

View File

@ -39,6 +39,7 @@ const PATCHES = {
},
// Set disableTelemetry() to true
/*
disableTelemetry(str: string) {
let text = '.disableTelemetry=function(){return!1}';
if (!str.includes(text)) {
@ -47,6 +48,7 @@ const PATCHES = {
return str.replace(text, '.disableTelemetry=function(){return!0}');
},
*/
disableTelemetryProvider(str: string) {
let text = 'this.enableLightweightTelemetry=!';
@ -91,14 +93,14 @@ const PATCHES = {
return str.replace(text, `?"${layout}":"${layout}"`);
},
// Replace "/direct-connect" with "/play"
remotePlayDirectConnectUrl(str: string) {
const index = str.indexOf('/direct-connect');
if (index < 0) {
remotePlayPostStreamRedirectUrl(str: string) {
let text = '.RemotePlayRoot.getLink()):';
if (!str.includes(text)) {
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) {
@ -202,8 +204,11 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
},
enableXcloudLogger(str: string) {
let text = 'this.telemetryProvider=e}log(e,t,r){';
if (!str.includes(text)) {
let index = str.indexOf('this.telemetryProvider.trackErrorLike');
index > -1 && (index = PatcherUtils.lastIndexOf(str, '}log(', index, 1500));
index > -1 && (index = PatcherUtils.indexOf(str, '{', index, 30, true));
if (index < 0) {
return false;
}
@ -213,7 +218,7 @@ const logFunc = [console.debug, console.log, console.warn, console.error][logLev
logFunc(logTag, '//', logMessage);
`;
str = str.replaceAll(text, text + newCode);
str = PatcherUtils.insertAt(str, index, newCode);
return str;
},
@ -909,20 +914,6 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
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) {
let text = '=window.__PRELOADED_STATE__;';
if (!str.includes(text)) {
@ -1219,8 +1210,6 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
'patchGamepadPolling',
'optimizeGameSlugGenerator',
'modifyPreloadedState',
'detectBrowserRouterReady',
@ -1269,7 +1258,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
...(getGlobalPref(GlobalPref.BLOCK_TRACKING) ? [
'disableAiTrack',
'disableTelemetry',
// 'disableTelemetry',
'blockWebRtcStatsCollector',
'disableIndexDbLogging',
@ -1278,7 +1267,6 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
] : []) as PatchArray,
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [
'remotePlayDirectConnectUrl',
'remotePlayKeepAlive',
'remotePlayDisableAchievementToast',
STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync',
@ -1348,6 +1336,7 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
getGlobalPref(GlobalPref.STREAM_COMBINE_SOURCES) && 'streamCombineSources',
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [
'remotePlayPostStreamRedirectUrl',
'patchRemotePlayMkb',
'remotePlayConnectMode',
] : []) as PatchArray,

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
for (const region of this.regions) {
try {

View File

@ -209,7 +209,7 @@ export class TouchController {
}
if (!layoutId) {
BxLogger.error(LOG_TAG, 'Invalid layoutId, show default controller');
BxLogger.warning(LOG_TAG, 'Invalid layoutId, show default controller');
TouchController.#enabled && TouchController.#showDefault();
return;
}

View File

@ -55,9 +55,9 @@ export function patchVideoApi() {
return nativePlay.apply(this);
}
const $parent = this.parentElement!!;
const $parent = this.parentElement;
// 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 });
}

View File

@ -49,9 +49,12 @@ function updateIceCandidates(candidates: any, options: { preferIpv6Server: boole
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);
}
}
if (options.preferIpv6Server) {
lst.sort((a, b) => {

View File

@ -7,6 +7,7 @@ export const SUPPORTED_LANGUAGES = {
'en-US': 'English (US)',
'ca-CA': 'Català',
'cs-CZ': 'čeština',
'da-DK': 'dansk',
'de-DE': 'Deutsch',
'en-ID': 'Bahasa Indonesia',
@ -198,6 +199,7 @@ const Texts = {
"new-version-available": [
(e: any) => `Version ${e.version} available`,
(e: any) => `Versió ${e.version} disponible`,
(e: any) => `Verze ${e.version} dostupná`,
,
(e: any) => `Version ${e.version} verfügbar`,
(e: any) => `Versi ${e.version} tersedia`,
@ -243,6 +245,7 @@ const Texts = {
"press-key-to-toggle-mkb": [
(e: any) => `Press ${e.key} to toggle this feature`,
(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) => `${e.key}: Funktion an-/ausschalten`,
(e: any) => `Tekan ${e.key} untuk mengaktifkan fitur ini`,
@ -268,6 +271,7 @@ const Texts = {
(e: any) => `Recommended settings for ${e.device}`,
(e: any) => `Configuració recomanada per a ${e.device}`,
,
,
(e: any) => `Empfohlene Einstellungen für ${e.device}`,
(e: any) => `Rekomendasi pengaturan untuk ${e.device}`,
(e: any) => `Ajustes recomendados para ${e.device}`,
@ -378,6 +382,7 @@ const Texts = {
"touch-control-layout-by": [
(e: any) => `Touch control layout by ${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-Steuerungslayout von ${e.name}`,
(e: any) => `Tata letak Sentuhan layar oleh ${e.name}`,

View File

@ -16,6 +16,15 @@ export function checkForUpdate() {
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 currentVersion = getGlobalPref(GlobalPref.VERSION_CURRENT);
@ -28,13 +37,6 @@ export function checkForUpdate() {
// Start checking
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
Translations.updateTranslations(currentVersion === SCRIPT_VERSION);

View File

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