Optimize Patcher

This commit is contained in:
redphx 2024-12-06 18:13:24 +07:00
parent fe696043f8
commit 1ea1afe4d4
14 changed files with 207 additions and 188 deletions

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud (Lite)
// @namespace https://github.com/redphx
// @version 6.0.0
// @version 6.0.1-beta-1
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@ -105,7 +105,7 @@ class UserAgent {
});
}
}
var SCRIPT_VERSION = "6.0.0", SCRIPT_VARIANT = "lite", AppInterface = window.AppInterface;
var SCRIPT_VERSION = "6.0.1-beta-1", SCRIPT_VARIANT = "lite", 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,
@ -4775,7 +4775,8 @@ var BxExposed = {
/ {2,}/g,
/ /g
],
toggleLocalCoOp: (enable) => {}
toggleLocalCoOp(enable) {},
beforePageLoad: () => {}
};
function localRedirect(path) {
let url = window.location.href.substring(0, 31) + path, $pageContent = document.getElementById("PageContent");
@ -6159,35 +6160,6 @@ function patchCanvasContext() {
return nativeGetContext.apply(this, [contextType, contextAttributes]);
};
}
class ProductDetailsPage {
static $btnShortcut = AppInterface && createButton({
icon: BxIcon.CREATE_SHORTCUT,
label: t("create-shortcut"),
style: 64,
onClick: (e) => {
AppInterface.createShortcut(window.location.pathname.substring(6));
}
});
static $btnWallpaper = AppInterface && createButton({
icon: BxIcon.DOWNLOAD,
label: t("wallpaper"),
style: 64,
onClick: (e) => {
let details = parseDetailsPath(window.location.pathname);
details && AppInterface.downloadWallpapers(details.titleSlug, details.productId);
}
});
static injectTimeoutId = null;
static injectButtons() {
if (!AppInterface) return;
ProductDetailsPage.injectTimeoutId && clearTimeout(ProductDetailsPage.injectTimeoutId), ProductDetailsPage.injectTimeoutId = window.setTimeout(() => {
let $container = document.querySelector("div[class*=ActionButtons-module__container]");
if ($container && $container.parentElement) $container.parentElement.appendChild(CE("div", {
class: "bx-product-details-buttons"
}, BX_FLAGS.DeviceInfo.deviceType === "android" && ProductDetailsPage.$btnShortcut, ProductDetailsPage.$btnWallpaper));
}, 500);
}
}
class StreamUiHandler {
static $btnStreamSettings;
static $btnStreamStats;
@ -6422,9 +6394,6 @@ window.addEventListener(BxEvent.STREAM_PLAYING, (e) => {
window.addEventListener(BxEvent.STREAM_ERROR_PAGE, (e) => {
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
});
window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, (e) => {
if (e.component === "product-details") ProductDetailsPage.injectButtons();
});
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => {
let dataChannel = e.dataChannel;
if (!dataChannel || dataChannel.label !== "message") return;

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 6.0.0
// @version 6.0.1-beta-1
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@ -107,7 +107,7 @@ class UserAgent {
});
}
}
var SCRIPT_VERSION = "6.0.0", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface;
var SCRIPT_VERSION = "6.0.1-beta-1", 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,
@ -4063,8 +4063,16 @@ class PatcherUtils {
static replaceWith(txt, index, fromString, toString) {
return txt.substring(0, index) + toString + txt.substring(index + fromString.length);
}
static filterPatches(patches) {
return patches.filter((item2) => !!item2);
}
static patchBeforePageLoad(str, page) {
let text = `chunkName:()=>"${page}-page",`;
if (!str.includes(text)) return !1;
return str = str.replace("requireAsync(e){", `requireAsync(e){window.BX_EXPOSED.beforePageLoad("${page}");`), str = str.replace("requireSync(e){", `requireSync(e){window.BX_EXPOSED.beforePageLoad("${page}");`), str;
}
}
var ENDING_CHUNKS_PATCH_NAME = "loadingEndingChunks", LOG_TAG2 = "Patcher", PATCHES = {
var LOG_TAG2 = "Patcher", PATCHES = {
disableAiTrack(str) {
let text = ".track=function(", index = str.indexOf(text);
if (index < 0) return !1;
@ -4196,11 +4204,6 @@ logFunc(logTag, '//', logMessage);
let newCode = "e.enableTouchInput = true;";
return str = str.replace(text, text + newCode), str;
},
loadingEndingChunks(str) {
let text = '"FamilySagaManager"';
if (!str.includes(text)) return !1;
return BxLogger.info(LOG_TAG2, "Remaining patches:", PATCH_ORDERS), PATCH_ORDERS = PATCH_ORDERS.concat(PLAYING_PATCH_ORDERS), str;
},
disableStreamGate(str) {
let index = str.indexOf('case"partially-ready":');
if (index < 0) return !1;
@ -4471,10 +4474,10 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
if (index < 0) return !1;
return index = str.indexOf("{", index) + 1, str = str.substring(0, index) + set_currently_focused_interactable_default + str.substring(index), str;
},
detectProductDetailsPage(str) {
detectProductDetailPage(str) {
let index = str.indexOf('{location:"ProductDetailPage",');
if (index >= 0 && (index = PatcherUtils.lastIndexOf("return", str, index, 200)), index < 0) return !1;
return str = str.substring(0, index) + 'BxEvent.dispatch(window, BxEvent.XCLOUD_RENDERING_COMPONENT, { component: "product-details" });' + str.substring(index), str;
if (index >= 0 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;
return str = str.substring(0, index) + 'BxEvent.dispatch(window, BxEvent.XCLOUD_RENDERING_COMPONENT, { component: "product-detail" });' + str.substring(index), str;
},
detectBrowserRouterReady(str) {
let index = str.indexOf("{history:this.history,");
@ -4501,8 +4504,17 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
let text = "=window.__PRELOADED_STATE__;";
if (!str.includes(text)) return !1;
return str = str.replace(text, "=window.BX_EXPOSED.modifyPreloadedState(window.__PRELOADED_STATE__);"), str;
},
homePageBeforeLoad(str) {
return PatcherUtils.patchBeforePageLoad(str, "home");
},
productDetailPageBeforeLoad(str) {
return PatcherUtils.patchBeforePageLoad(str, "product-detail");
},
streamPageBeforeLoad(str) {
return PatcherUtils.patchBeforePageLoad(str, "stream");
}
}, PATCH_ORDERS = [
}, PATCH_ORDERS = PatcherUtils.filterPatches([
...getPref("nativeMkb.mode") === "on" ? [
"enableNativeMkb",
"exposeInputSink"
@ -4517,6 +4529,9 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
"patchGamepadPolling",
"exposeStreamSession",
"exposeDialogRoutes",
"homePageBeforeLoad",
"productDetailPageBeforeLoad",
"streamPageBeforeLoad",
"guideAchievementsDefaultLocked",
"enableTvRoutes",
"supportLocalCoOp",
@ -4524,10 +4539,6 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
getPref("ui.gameCard.waitTime.show") && "patchSetCurrentlyFocusedInteractable",
getPref("ui.layout") !== "default" && "websiteLayout",
getPref("game.fortnite.forceConsole") && "forceFortniteConsole",
getPref("ui.hideSections").includes("friends") && "ignorePlayWithFriendsSection",
getPref("ui.hideSections").includes("all-games") && "ignoreAllGamesSection",
getPref("ui.hideSections").includes("touch") && "ignorePlayWithTouchSection",
(getPref("ui.hideSections").includes("native-mkb") || getPref("ui.hideSections").includes("most-popular")) && "ignoreSiglSections",
...STATES.userAgent.capabilities.touch ? [
"disableTouchContextMenu"
] : [],
@ -4550,7 +4561,12 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
"enableConsoleLogging",
"enableXcloudLogger"
] : []
].filter((item2) => !!item2), PLAYING_PATCH_ORDERS = [
]), HOME_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
getPref("ui.hideSections").includes("friends") && "ignorePlayWithFriendsSection",
getPref("ui.hideSections").includes("all-games") && "ignoreAllGamesSection",
STATES.browser.capabilities.touch && getPref("ui.hideSections").includes("touch") && "ignorePlayWithTouchSection",
(getPref("ui.hideSections").includes("native-mkb") || getPref("ui.hideSections").includes("most-popular")) && "ignoreSiglSections"
]), STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
"patchXcloudTitleInfo",
"disableGamepadDisconnectedScreen",
"patchStreamHud",
@ -4562,7 +4578,7 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
...STATES.userAgent.capabilities.touch ? [
getPref("touchController.mode") === "all" && "patchShowSensorControls",
getPref("touchController.mode") === "all" && "exposeTouchLayoutManager",
(getPref("touchController.mode") === "off" || getPref("touchController.autoOff") || !STATES.userAgent.capabilities.touch) && "disableTakRenderer",
(getPref("touchController.mode") === "off" || getPref("touchController.autoOff")) && "disableTakRenderer",
getPref("touchController.opacity.default") !== 100 && "patchTouchControlDefaultOpacity",
"patchBabylonRendererClass"
] : [],
@ -4577,9 +4593,21 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
"patchMouseAndKeyboardEnabled",
"disableNativeRequestPointerLock"
] : []
].filter((item2) => !!item2), ALL_PATCHES = [...PATCH_ORDERS, ...PLAYING_PATCH_ORDERS];
]), PRODUCT_DETAIL_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
AppInterface && "detectProductDetailPage"
]), ALL_PATCHES = [...PATCH_ORDERS, ...HOME_PAGE_PATCH_ORDERS, ...STREAM_PAGE_PATCH_ORDERS, ...PRODUCT_DETAIL_PAGE_PATCH_ORDERS];
class Patcher {
static #patchFunctionBind() {
static remainingPatches = {
home: HOME_PAGE_PATCH_ORDERS,
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;
@ -4587,7 +4615,7 @@ class Patcher {
if (arguments[1] === 0 || typeof arguments[1] === "function") valid = !0;
}
if (!valid) return nativeBind.apply(this, arguments);
if (PatcherCache.getInstance().init(), typeof arguments[1] === "function") BxLogger.info(LOG_TAG2, "Restored Function.prototype.bind()"), Function.prototype.bind = nativeBind;
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);
};
@ -4609,7 +4637,7 @@ class Patcher {
if (!PATCHES[patchName]) continue;
let tmpStr = PATCHES[patchName].call(null, patchedFuncStr);
if (!tmpStr) continue;
modified = !0, patchedFuncStr = tmpStr, BxLogger.info(LOG_TAG2, `${patchName}`), appliedPatches.push(patchName), patchesToCheck.splice(patchIndex, 1), patchIndex--, PATCH_ORDERS = PATCH_ORDERS.filter((item2) => item2 != patchName);
modified = !0, patchedFuncStr = tmpStr, BxLogger.info(LOG_TAG2, `${patchName}`), appliedPatches.push(patchName), patchesToCheck.splice(patchIndex, 1), patchIndex--, PATCH_ORDERS = PATCH_ORDERS.filter((item2) => item2 != patchName), BxLogger.info(LOG_TAG2, "Remaining patches", PATCH_ORDERS);
}
if (modified) try {
chunkData[chunkId] = eval(patchedFuncStr);
@ -4621,7 +4649,7 @@ class Patcher {
if (Object.keys(patchesMap).length) patcherCache.saveToCache(patchesMap);
}
static init() {
Patcher.#patchFunctionBind();
Patcher.patchNativeBind();
}
}
class PatcherCache {
@ -4630,7 +4658,14 @@ class PatcherCache {
KEY_CACHE = "BetterXcloud.Patches.Cache";
KEY_SIGNATURE = "BetterXcloud.Patches.Cache.Signature";
CACHE;
isInitialized = !1;
constructor() {
this.checkSignature(), this.CACHE = JSON.parse(window.localStorage.getItem(this.KEY_CACHE) || "{}"), BxLogger.info(LOG_TAG2, "Cache", this.CACHE);
let pathName = window.location.pathname;
if (pathName.includes("/play/launch/")) Patcher.patchPage("stream");
else if (pathName.includes("/play/games/")) Patcher.patchPage("product-detail");
else if (pathName.endsWith("/play") || pathName.endsWith("/play/")) Patcher.patchPage("home");
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() {
let scriptVersion = SCRIPT_VERSION, patches = JSON.stringify(ALL_PATCHES), webVersion = "", $link = document.querySelector('link[data-chunk="client"][href*="/client."]');
if ($link) {
@ -4666,12 +4701,6 @@ class PatcherCache {
}
window.localStorage.setItem(this.KEY_CACHE, JSON.stringify(this.CACHE));
}
init() {
if (this.isInitialized) return;
if (this.isInitialized = !0, this.checkSignature(), this.CACHE = JSON.parse(window.localStorage.getItem(this.KEY_CACHE) || "{}"), BxLogger.info(LOG_TAG2, this.CACHE), window.location.pathname.includes("/play/")) PATCH_ORDERS.push(...PLAYING_PATCH_ORDERS);
else PATCH_ORDERS.push(ENDING_CHUNKS_PATCH_NAME);
PATCH_ORDERS = this.cleanupPatches(PATCH_ORDERS), PLAYING_PATCH_ORDERS = this.cleanupPatches(PLAYING_PATCH_ORDERS), BxLogger.info(LOG_TAG2, PATCH_ORDERS.slice(0)), BxLogger.info(LOG_TAG2, PLAYING_PATCH_ORDERS.slice(0));
}
}
class BxNumberStepper extends HTMLInputElement {
intervalId = null;
@ -6936,7 +6965,10 @@ var BxExposed = {
/ {2,}/g,
/ /g
],
toggleLocalCoOp: (enable) => {}
toggleLocalCoOp(enable) {},
beforePageLoad: (page) => {
BxLogger.info("beforePageLoad", page), Patcher.patchPage(page);
}
};
function localRedirect(path) {
let url = window.location.href.substring(0, 31) + path, $pageContent = document.getElementById("PageContent");
@ -9214,7 +9246,7 @@ window.addEventListener(BxEvent.STREAM_ERROR_PAGE, (e) => {
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
});
window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, (e) => {
if (e.component === "product-details") ProductDetailsPage.injectButtons();
if (e.component === "product-detail") ProductDetailsPage.injectButtons();
});
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => {
let dataChannel = e.dataChannel;

View File

@ -16,7 +16,7 @@ import { LoadingScreen } from "@modules/loading-screen";
import { MouseCursorHider } from "@modules/mkb/mouse-cursor-hider";
import { TouchController } from "@modules/touch-controller";
import { checkForUpdate, disablePwa, productTitleToSlug } from "@utils/utils";
import { Patcher } from "@modules/patcher";
import { Patcher } from "@/modules/patcher/patcher";
import { RemotePlayManager } from "@/modules/remote-play-manager";
import { onHistoryChanged, patchHistoryMethod } from "@utils/history";
import { disableAdobeAudienceManager, patchAudioContext, patchCanvasContext, patchMeControl, patchPointerLockApi, patchRtcCodecs, patchRtcPeerConnection, patchVideoApi } from "@utils/monkey-patches";
@ -266,9 +266,9 @@ window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => {
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
});
window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
isFullVersion() && window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
const component = (e as any).component;
if (component === 'product-details') {
if (component === 'product-detail') {
ProductDetailsPage.injectButtons();
}
});

View File

@ -0,0 +1,45 @@
import type { PatchArray, PatchName, PatchPage } from "./patcher";
export class PatcherUtils {
static indexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
const index = txt.indexOf(searchString, startIndex);
if (index < 0 || (maxRange && index - startIndex > maxRange)) {
return -1;
}
return index;
}
static lastIndexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
const index = txt.lastIndexOf(searchString, startIndex);
if (index < 0 || (maxRange && startIndex - index > maxRange)) {
return -1;
}
return index;
}
static insertAt(txt: string, index: number, insertString: string): string {
return txt.substring(0, index) + insertString + txt.substring(index);
}
static replaceWith(txt: string, index: number, fromString: string, toString: string): string {
return txt.substring(0, index) + toString + txt.substring(index + fromString.length);
}
static filterPatches(patches: Array<string | false>): PatchArray {
return patches.filter((item): item is PatchName => !!item);
}
static patchBeforePageLoad(str: string, page: PatchPage): string | false {
let text = `chunkName:()=>"${page}-page",`;
if (!str.includes(text)) {
return false;
}
str = str.replace('requireAsync(e){', `requireAsync(e){window.BX_EXPOSED.beforePageLoad("${page}");`);
str = str.replace('requireSync(e){', `requireSync(e){window.BX_EXPOSED.beforePageLoad("${page}");`);
return str;
}
}

View File

@ -1,4 +1,4 @@
import { SCRIPT_VERSION, STATES } from "@utils/global";
import { AppInterface, SCRIPT_VERSION, STATES } from "@utils/global";
import { BX_FLAGS } from "@utils/bx-flags";
import { BxLogger } from "@utils/bx-logger";
import { hashCode, renderString } from "@utils/utils";
@ -17,39 +17,12 @@ import { getPref } from "@/utils/settings-storages/global-settings-storage";
import { GamePassCloudGallery } from "@/enums/game-pass-gallery";
import { t } from "@/utils/translation";
import { NativeMkbMode, TouchControllerMode, UiLayout, UiSection } from "@/enums/pref-values";
import { PatcherUtils } from "./patcher-utils.js";
type PathName = keyof typeof PATCHES;
type PatchArray = PathName[];
export type PatchName = keyof typeof PATCHES;
export type PatchArray = PatchName[];
export type PatchPage = 'home' | 'stream' | 'product-detail';
class PatcherUtils {
static indexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
const index = txt.indexOf(searchString, startIndex);
if (index < 0 || (maxRange && index - startIndex > maxRange)) {
return -1;
}
return index;
}
static lastIndexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
const index = txt.lastIndexOf(searchString, startIndex);
if (index < 0 || (maxRange && startIndex - index > maxRange)) {
return -1;
}
return index;
}
static insertAt(txt: string, index: number, insertString: string): string {
return txt.substring(0, index) + insertString + txt.substring(index);
}
static replaceWith(txt: string, index: number, fromString: string, toString: string): string {
return txt.substring(0, index) + toString + txt.substring(index + fromString.length);
}
}
const ENDING_CHUNKS_PATCH_NAME = 'loadingEndingChunks';
const LOG_TAG = 'Patcher';
const PATCHES = {
@ -314,19 +287,6 @@ logFunc(logTag, '//', logMessage);
return str;
},
// Add patches that are only needed when start playing
loadingEndingChunks(str: string) {
let text = '"FamilySagaManager"';
if (!str.includes(text)) {
return false;
}
BxLogger.info(LOG_TAG, 'Remaining patches:', PATCH_ORDERS);
PATCH_ORDERS = PATCH_ORDERS.concat(PLAYING_PATCH_ORDERS);
return str;
},
// Disable StreamGate
disableStreamGate(str: string) {
const index = str.indexOf('case"partially-ready":');
@ -719,30 +679,6 @@ true` + text;
return str;
},
/*
(x.AW, {
path: V.LoginDeviceCode.path,
exact: !0,
render: () => (0, n.jsx)(qe, {
children: (0, n.jsx)(Et.R, {})
})
}, V.LoginDeviceCode.name),
const qe = e => {
let {
children: t
} = e;
const {
isTV: a,
isSupportedTVBrowser: r
} = (0, T.d)();
return a && r ? (0, n.jsx)(n.Fragment, {
children: t
}) : (0, n.jsx)(x.l_, {
to: V.Home.getLink()
})
};
*/
enableTvRoutes(str: string) {
let index = str.indexOf('.LoginDeviceCode.path,');
if (index < 0) {
@ -912,16 +848,15 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
return str;
},
// product-details-page.js#2388, 24.17.20
detectProductDetailsPage(str: string) {
detectProductDetailPage(str: string) {
let index = str.indexOf('{location:"ProductDetailPage",');
index >= 0 && (index = PatcherUtils.lastIndexOf('return', str, index, 200));
index >= 0 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 200));
if (index < 0) {
return false;
}
str = str.substring(0, index) + 'BxEvent.dispatch(window, BxEvent.XCLOUD_RENDERING_COMPONENT, { component: "product-details" });' + str.substring(index);
str = str.substring(0, index) + 'BxEvent.dispatch(window, BxEvent.XCLOUD_RENDERING_COMPONENT, { component: "product-detail" });' + str.substring(index);
return str;
},
@ -992,9 +927,21 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
str = str.replace(text, '=window.BX_EXPOSED.modifyPreloadedState(window.__PRELOADED_STATE__);');
return str;
},
homePageBeforeLoad(str: string) {
return PatcherUtils.patchBeforePageLoad(str, 'home');
},
productDetailPageBeforeLoad(str: string) {
return PatcherUtils.patchBeforePageLoad(str, 'product-detail');
},
streamPageBeforeLoad(str: string) {
return PatcherUtils.patchBeforePageLoad(str, 'stream');
},
};
let PATCH_ORDERS: PatchArray = [
let PATCH_ORDERS = PatcherUtils.filterPatches([
...(getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
'enableNativeMkb',
'exposeInputSink',
@ -1015,10 +962,13 @@ let PATCH_ORDERS: PatchArray = [
'exposeStreamSession',
'exposeDialogRoutes',
'homePageBeforeLoad',
'productDetailPageBeforeLoad',
'streamPageBeforeLoad',
'guideAchievementsDefaultLocked',
'enableTvRoutes',
// AppInterface && 'detectProductDetailsPage',
'supportLocalCoOp',
'overrideStorageGetSettings',
@ -1027,11 +977,6 @@ let PATCH_ORDERS: PatchArray = [
getPref<UiLayout>(PrefKey.UI_LAYOUT) !== UiLayout.DEFAULT && 'websiteLayout',
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS) && 'ignorePlayWithFriendsSection',
getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
(getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.NATIVE_MKB) || getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.MOST_POPULAR)) && 'ignoreSiglSections',
...(STATES.userAgent.capabilities.touch ? [
'disableTouchContextMenu',
] : []),
@ -1059,12 +1004,19 @@ let PATCH_ORDERS: PatchArray = [
'enableConsoleLogging',
'enableXcloudLogger',
] : []),
].filter((item): item is string => !!item) as PatchArray;
]);
let HOME_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS) && 'ignorePlayWithFriendsSection',
getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
STATES.browser.capabilities.touch && getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
(getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.NATIVE_MKB) || getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.MOST_POPULAR)) && 'ignoreSiglSections',
]);
// Only when playing
// TODO: check this
// @ts-ignore
let PLAYING_PATCH_ORDERS: PatchArray = [
let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
'patchXcloudTitleInfo',
'disableGamepadDisconnectedScreen',
'patchStreamHud',
@ -1085,7 +1037,7 @@ let PLAYING_PATCH_ORDERS: PatchArray = [
...(STATES.userAgent.capabilities.touch ? [
getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'patchShowSensorControls',
getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'exposeTouchLayoutManager',
(getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF || getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF) || !STATES.userAgent.capabilities.touch) && 'disableTakRenderer',
(getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF || getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
getPref<TouchControllerDefaultOpacity>(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) !== 100 && 'patchTouchControlDefaultOpacity',
'patchBabylonRendererClass',
] : []),
@ -1106,12 +1058,32 @@ let PLAYING_PATCH_ORDERS: PatchArray = [
'patchMouseAndKeyboardEnabled',
'disableNativeRequestPointerLock',
] : []),
].filter((item): item is string => !!item);
]);
const ALL_PATCHES = [...PATCH_ORDERS, ...PLAYING_PATCH_ORDERS];
let PRODUCT_DETAIL_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
AppInterface && 'detectProductDetailPage',
]);
const ALL_PATCHES = [...PATCH_ORDERS, ...HOME_PAGE_PATCH_ORDERS, ...STREAM_PAGE_PATCH_ORDERS, ...PRODUCT_DETAIL_PAGE_PATCH_ORDERS];
export class Patcher {
static #patchFunctionBind() {
private static remainingPatches: { [key in PatchPage]: PatchArray } = {
home: HOME_PAGE_PATCH_ORDERS,
stream: STREAM_PAGE_PATCH_ORDERS,
'product-detail': PRODUCT_DETAIL_PAGE_PATCH_ORDERS,
};
static patchPage(page: PatchPage) {
const remaining = Patcher.remainingPatches[page];
if (!remaining) {
return;
}
PATCH_ORDERS = PATCH_ORDERS.concat(remaining);
delete Patcher.remainingPatches[page];
}
private static patchNativeBind() {
const nativeBind = Function.prototype.bind;
Function.prototype.bind = function() {
let valid = false;
@ -1132,8 +1104,6 @@ export class Patcher {
return nativeBind.apply(this, arguments);
}
PatcherCache.getInstance().init();
if (typeof arguments[1] === 'function') {
BxLogger.info(LOG_TAG, 'Restored Function.prototype.bind()');
Function.prototype.bind = nativeBind;
@ -1211,6 +1181,7 @@ export class Patcher {
patchesToCheck.splice(patchIndex, 1);
patchIndex--;
PATCH_ORDERS = PATCH_ORDERS.filter(item => item != patchName);
BxLogger.info(LOG_TAG, 'Remaining patches', PATCH_ORDERS);
}
// Apply patched functions
@ -1236,7 +1207,7 @@ export class Patcher {
}
static init() {
Patcher.#patchFunctionBind();
Patcher.patchNativeBind();
}
}
@ -1249,7 +1220,29 @@ export class PatcherCache {
private CACHE!: { [key: string]: PatchArray };
private isInitialized = false;
private constructor() {
this.checkSignature();
// Read cache from storage
this.CACHE = JSON.parse(window.localStorage.getItem(this.KEY_CACHE) || '{}');
BxLogger.info(LOG_TAG, 'Cache', this.CACHE);
const pathName = window.location.pathname;
if (pathName.includes('/play/launch/')) {
Patcher.patchPage('stream');
} else if (pathName.includes('/play/games/')) {
Patcher.patchPage('product-detail');
} else if (pathName.endsWith('/play') || pathName.endsWith('/play/')) {
Patcher.patchPage('home');
}
// Remove cached patches from PATCH_ORDERS & PLAYING_PATCH_ORDERS
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_TAG, 'PATCH_ORDERS', PATCH_ORDERS.slice(0));
}
/**
* Get patch's signature
@ -1333,30 +1326,4 @@ export class PatcherCache {
// Save to storage
window.localStorage.setItem(this.KEY_CACHE, JSON.stringify(this.CACHE));
}
init() {
if (this.isInitialized) {
return;
}
this.isInitialized = true;
this.checkSignature();
// Read cache from storage
this.CACHE = JSON.parse(window.localStorage.getItem(this.KEY_CACHE) || '{}');
BxLogger.info(LOG_TAG, this.CACHE);
if (window.location.pathname.includes('/play/')) {
PATCH_ORDERS.push(...PLAYING_PATCH_ORDERS);
} else {
PATCH_ORDERS.push(ENDING_CHUNKS_PATCH_NAME);
}
// Remove cached patches from PATCH_ORDERS & PLAYING_PATCH_ORDERS
PATCH_ORDERS = this.cleanupPatches(PATCH_ORDERS);
PLAYING_PATCH_ORDERS = this.cleanupPatches(PLAYING_PATCH_ORDERS);
BxLogger.info(LOG_TAG, PATCH_ORDERS.slice(0));
BxLogger.info(LOG_TAG, PLAYING_PATCH_ORDERS.slice(0));
}
}

View File

@ -12,7 +12,7 @@ import { STATES, AppInterface, deepClone, SCRIPT_VERSION, STORAGE, SCRIPT_VARIAN
import { t, Translations } from "@/utils/translation";
import { BxSelectElement } from "@/web-components/bx-select";
import { setNearby } from "@/utils/navigation-utils";
import { PatcherCache } from "@/modules/patcher";
import { PatcherCache } from "@/modules/patcher/patcher";
import { UserAgentProfile } from "@/enums/user-agent";
import { UserAgent } from "@/utils/user-agent";
import { BX_FLAGS } from "@/utils/bx-flags";

View File

@ -11,6 +11,7 @@ import { getPref } from "./settings-storages/global-settings-storage";
import { GamePassCloudGallery } from "@/enums/game-pass-gallery";
import { TouchController } from "@/modules/touch-controller";
import { NativeMkbMode, TouchControllerMode } from "@/enums/pref-values";
import { Patcher, type PatchPage } from "@/modules/patcher/patcher";
export enum SupportedInputType {
CONTROLLER = 'Controller',
@ -208,5 +209,10 @@ export const BxExposed = {
/ /g,
],
toggleLocalCoOp: (enable: boolean) => {},
toggleLocalCoOp(enable: boolean) {},
beforePageLoad: isFullVersion() ? (page: PatchPage) => {
BxLogger.info('beforePageLoad', page);
Patcher.patchPage(page);
} : () => {},
};