mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-03 06:07:19 +02:00
Optimize Patcher
This commit is contained in:
parent
fe696043f8
commit
1ea1afe4d4
39
dist/better-xcloud.lite.user.js
vendored
39
dist/better-xcloud.lite.user.js
vendored
@ -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;
|
||||
|
96
dist/better-xcloud.user.js
vendored
96
dist/better-xcloud.user.js
vendored
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
45
src/modules/patcher/patcher-utils.ts
Normal file
45
src/modules/patcher/patcher-utils.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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";
|
||||
|
@ -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);
|
||||
} : () => {},
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user