mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-06 23:57:19 +02:00
Add PatcherCache class
This commit is contained in:
parent
43e6f3083e
commit
ea57e04d4f
@ -232,7 +232,7 @@ function main() {
|
||||
StreamStats.setupEvents();
|
||||
MkbHandler.setupEvents();
|
||||
|
||||
Patcher.initialize();
|
||||
Patcher.init();
|
||||
|
||||
disablePwa();
|
||||
|
||||
|
@ -1,8 +1,13 @@
|
||||
import { STATES } from "@utils/global";
|
||||
import { SCRIPT_VERSION, STATES } from "@utils/global";
|
||||
import { BX_FLAGS } from "@utils/bx-flags";
|
||||
import { getPref, PrefKey } from "@utils/preferences";
|
||||
import { VibrationManager } from "@modules/vibration-manager";
|
||||
import { BxLogger } from "@utils/bx-logger";
|
||||
import { hashCode } from "@/utils/utils";
|
||||
|
||||
type PatchArray = (keyof typeof PATCHES)[];
|
||||
|
||||
const ENDING_CHUNKS_PATCH_NAME = 'loadingEndingChunks';
|
||||
|
||||
const LOG_TAG = 'Patcher';
|
||||
|
||||
@ -110,16 +115,6 @@ const PATCHES = {
|
||||
return str.replace(text, `connectMode:window.BX_REMOTE_PLAY_CONFIG?"xhome-connect":"cloud-connect",remotePlayServerId:(window.BX_REMOTE_PLAY_CONFIG&&window.BX_REMOTE_PLAY_CONFIG.serverId)||''`);
|
||||
},
|
||||
|
||||
// Fix the Guide/Nexus button not working in Remote Play
|
||||
remotePlayGuideWorkaround(str: string) {
|
||||
const text = 'nexusButtonHandler:this.featureGates.EnableClientGuideInStream';
|
||||
if (!str.includes(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return str.replace(text, `nexusButtonHandler: !window.BX_REMOTE_PLAY_CONFIG && this.featureGates.EnableClientGuideInStream`);
|
||||
},
|
||||
|
||||
// Disable trackEvent() function
|
||||
disableTrackEvent(str: string) {
|
||||
const text = 'this.trackEvent=';
|
||||
@ -241,14 +236,13 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
|
||||
|
||||
// Add patches that are only needed when start playing
|
||||
loadingEndingChunks(str: string) {
|
||||
const text = 'Symbol("ChatSocketPlugin")';
|
||||
const text = '"FamilySagaManager"';
|
||||
if (!str.includes(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BxLogger.info(LOG_TAG, 'Remaining patches:', PATCH_ORDERS);
|
||||
PATCH_ORDERS = PATCH_ORDERS.concat(PLAYING_PATCH_ORDERS);
|
||||
Patcher.cleanupPatches();
|
||||
|
||||
return str;
|
||||
},
|
||||
@ -427,72 +421,61 @@ BxLogger.info('patchRemotePlayMkb', ${configsVar});
|
||||
},
|
||||
};
|
||||
|
||||
let PATCH_ORDERS = [
|
||||
getPref(PrefKey.BLOCK_TRACKING) && [
|
||||
let PATCH_ORDERS: PatchArray = [
|
||||
'disableStreamGate',
|
||||
'overrideSettings',
|
||||
'broadcastPollingMode',
|
||||
|
||||
getPref(PrefKey.UI_LAYOUT) === 'tv' && 'tvLayout',
|
||||
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && 'supportLocalCoOp',
|
||||
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
|
||||
|
||||
...(getPref(PrefKey.BLOCK_TRACKING) ? [
|
||||
'disableAiTrack',
|
||||
'disableTelemetry',
|
||||
],
|
||||
|
||||
['disableStreamGate'],
|
||||
|
||||
['broadcastPollingMode'],
|
||||
|
||||
getPref(PrefKey.UI_LAYOUT) === 'tv' && ['tvLayout'],
|
||||
|
||||
BX_FLAGS.EnableXcloudLogging && [
|
||||
'enableConsoleLogging',
|
||||
'enableXcloudLogger',
|
||||
],
|
||||
|
||||
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && ['supportLocalCoOp'],
|
||||
|
||||
getPref(PrefKey.BLOCK_TRACKING) && [
|
||||
'blockWebRtcStatsCollector',
|
||||
'disableIndexDbLogging',
|
||||
],
|
||||
|
||||
getPref(PrefKey.BLOCK_TRACKING) && [
|
||||
'disableTelemetryProvider',
|
||||
'disableTrackEvent',
|
||||
],
|
||||
] : []),
|
||||
|
||||
getPref(PrefKey.REMOTE_PLAY_ENABLED) && ['remotePlayKeepAlive'],
|
||||
getPref(PrefKey.REMOTE_PLAY_ENABLED) && ['remotePlayDirectConnectUrl'],
|
||||
|
||||
[
|
||||
'overrideSettings',
|
||||
],
|
||||
|
||||
getPref(PrefKey.REMOTE_PLAY_ENABLED) && STATES.hasTouchSupport && ['patchUpdateInputConfigurationAsync'],
|
||||
|
||||
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && ['forceFortniteConsole'],
|
||||
];
|
||||
...(getPref(PrefKey.REMOTE_PLAY_ENABLED) ? [
|
||||
'remotePlayKeepAlive',
|
||||
'remotePlayDirectConnectUrl',
|
||||
STATES.hasTouchSupport && 'patchUpdateInputConfigurationAsync',
|
||||
] : []),
|
||||
|
||||
...(BX_FLAGS.EnableXcloudLogging ? [
|
||||
'enableConsoleLogging',
|
||||
'enableXcloudLogger',
|
||||
] : []),
|
||||
].filter(item => !!item);
|
||||
|
||||
// Only when playing
|
||||
const PLAYING_PATCH_ORDERS = [
|
||||
['patchXcloudTitleInfo'],
|
||||
getPref(PrefKey.REMOTE_PLAY_ENABLED) && ['patchRemotePlayMkb'],
|
||||
|
||||
getPref(PrefKey.REMOTE_PLAY_ENABLED) && ['remotePlayConnectMode'],
|
||||
getPref(PrefKey.REMOTE_PLAY_ENABLED) && ['remotePlayGuideWorkaround'],
|
||||
|
||||
['patchStreamHud'],
|
||||
|
||||
['playVibration'],
|
||||
STATES.hasTouchSupport && getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && ['exposeTouchLayoutManager'],
|
||||
STATES.hasTouchSupport && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'off' || getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) && ['disableTakRenderer'],
|
||||
|
||||
BX_FLAGS.EnableXcloudLogging && ['enableConsoleLogging'],
|
||||
|
||||
getPref(PrefKey.BLOCK_TRACKING) && ['blockGamepadStatsCollector'],
|
||||
|
||||
[
|
||||
let PLAYING_PATCH_ORDERS: PatchArray = [
|
||||
'patchXcloudTitleInfo',
|
||||
'disableGamepadDisconnectedScreen',
|
||||
],
|
||||
'patchStreamHud',
|
||||
'playVibration',
|
||||
|
||||
getPref(PrefKey.STREAM_COMBINE_SOURCES) && ['streamCombineSources'],
|
||||
];
|
||||
STATES.hasTouchSupport && getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && 'exposeTouchLayoutManager',
|
||||
STATES.hasTouchSupport && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'off' || getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
|
||||
|
||||
BX_FLAGS.EnableXcloudLogging && 'enableConsoleLogging',
|
||||
|
||||
getPref(PrefKey.BLOCK_TRACKING) && 'blockGamepadStatsCollector',
|
||||
|
||||
getPref(PrefKey.STREAM_COMBINE_SOURCES) && 'streamCombineSources',
|
||||
|
||||
...(getPref(PrefKey.REMOTE_PLAY_ENABLED) ? [
|
||||
'patchRemotePlayMkb',
|
||||
'remotePlayConnectMode',
|
||||
] : []),
|
||||
].filter(item => !!item);
|
||||
|
||||
const ALL_PATCHES = [...PATCH_ORDERS, ...PLAYING_PATCH_ORDERS];
|
||||
|
||||
export class Patcher {
|
||||
static #patchFunctionBind() {
|
||||
@ -517,11 +500,6 @@ export class Patcher {
|
||||
|
||||
const orgFunc = this;
|
||||
const newFunc = (a: any, item: any) => {
|
||||
if (Patcher.length() === 0) {
|
||||
orgFunc(a, item);
|
||||
return;
|
||||
}
|
||||
|
||||
Patcher.patch(item);
|
||||
orgFunc(a, item);
|
||||
}
|
||||
@ -533,96 +511,185 @@ export class Patcher {
|
||||
|
||||
static length() { return PATCH_ORDERS.length; };
|
||||
|
||||
static patch(item: any) {
|
||||
static patch(item: [[number], { [key: string]: () => {} }]) {
|
||||
// console.log('patch', '-----');
|
||||
let patchesToCheck: PatchArray;
|
||||
let appliedPatches;
|
||||
const caches: { [key: string]: string[] } = {};
|
||||
|
||||
for (let id in item[1]) {
|
||||
if (PATCH_ORDERS.length <= 0) {
|
||||
return;
|
||||
const cachedPatches = PatcherCache.getPatches(id);
|
||||
if (cachedPatches) {
|
||||
patchesToCheck = cachedPatches;
|
||||
patchesToCheck.push(...PATCH_ORDERS);
|
||||
} else {
|
||||
patchesToCheck = PATCH_ORDERS;
|
||||
}
|
||||
|
||||
// Empty patch list
|
||||
if (!patchesToCheck.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// console.log(patchesToCheck);
|
||||
|
||||
appliedPatches = [];
|
||||
const func = item[1][id];
|
||||
let str = func.toString();
|
||||
|
||||
for (let groupIndex = 0; groupIndex < PATCH_ORDERS.length; groupIndex++) {
|
||||
const group = PATCH_ORDERS[groupIndex];
|
||||
// console.log(id, str);
|
||||
|
||||
for (let groupIndex = 0; groupIndex < patchesToCheck.length; groupIndex++) {
|
||||
const patchName = patchesToCheck[groupIndex];
|
||||
let modified = false;
|
||||
|
||||
for (let patchIndex = 0; patchIndex < group.length; patchIndex++) {
|
||||
const patchName = group[patchIndex] as keyof typeof PATCHES;
|
||||
if (appliedPatches.indexOf(patchName) > -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const patchedstr = PATCHES[patchName].call(null, str);
|
||||
if (!patchedstr) {
|
||||
// Only stop if the first patch is failed
|
||||
if (patchIndex === 0) {
|
||||
break;
|
||||
} else {
|
||||
// Check function against patch
|
||||
const patchedStr = PATCHES[patchName].call(null, str);
|
||||
|
||||
// Not patched
|
||||
if (!patchedStr) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
modified = true;
|
||||
str = patchedstr;
|
||||
str = patchedStr;
|
||||
|
||||
BxLogger.info(LOG_TAG, `Applied "${patchName}" patch`);
|
||||
appliedPatches.push(patchName);
|
||||
|
||||
// Remove patch from group
|
||||
group.splice(patchIndex, 1);
|
||||
patchIndex--;
|
||||
}
|
||||
// Remove patch
|
||||
patchesToCheck.splice(groupIndex, 1);
|
||||
groupIndex--;
|
||||
PATCH_ORDERS = PATCH_ORDERS.filter(item => item != patchName);
|
||||
|
||||
// Apply patched functions
|
||||
if (modified) {
|
||||
item[1][id] = eval(str);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty group
|
||||
if (!group.length) {
|
||||
PATCH_ORDERS.splice(groupIndex, 1);
|
||||
groupIndex--;
|
||||
}
|
||||
}
|
||||
// Save to cache
|
||||
if (appliedPatches.length) {
|
||||
caches[id] = appliedPatches;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove disabled patches
|
||||
static cleanupPatches() {
|
||||
for (let groupIndex = PATCH_ORDERS.length - 1; groupIndex >= 0; groupIndex--) {
|
||||
const group = PATCH_ORDERS[groupIndex];
|
||||
if (group === false) {
|
||||
PATCH_ORDERS.splice(groupIndex, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let patchIndex = group.length - 1; patchIndex >= 0; patchIndex--) {
|
||||
const patchName = group[patchIndex] as keyof typeof PATCHES;
|
||||
if (!PATCHES[patchName]) {
|
||||
// Remove disabled patch
|
||||
group.splice(patchIndex, 1);
|
||||
if (Object.keys(caches).length) {
|
||||
PatcherCache.saveToCache(caches);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty group
|
||||
if (!group.length) {
|
||||
PATCH_ORDERS.splice(groupIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static initialize() {
|
||||
if (window.location.pathname.includes('/play/')) {
|
||||
PATCH_ORDERS = PATCH_ORDERS.concat(PLAYING_PATCH_ORDERS);
|
||||
} else {
|
||||
PATCH_ORDERS.push(['loadingEndingChunks']);
|
||||
}
|
||||
|
||||
Patcher.cleanupPatches();
|
||||
static init() {
|
||||
Patcher.#patchFunctionBind();
|
||||
}
|
||||
}
|
||||
|
||||
class PatcherCache {
|
||||
static #KEY_CACHE = 'better_xcloud_patches_cache';
|
||||
static #KEY_SIGNATURE = 'better_xcloud_patches_cache_signature';
|
||||
|
||||
static #CACHE: any;
|
||||
|
||||
/**
|
||||
* Get patch's signature
|
||||
*/
|
||||
static #getSignature(): number {
|
||||
const scriptVersion = SCRIPT_VERSION;
|
||||
const webVersion = (document.querySelector('meta[name=gamepass-app-version]') as HTMLMetaElement)?.content;
|
||||
const patches = JSON.stringify(ALL_PATCHES);
|
||||
|
||||
// Calculate signature
|
||||
const sig = hashCode(scriptVersion + webVersion + patches)
|
||||
return sig;
|
||||
}
|
||||
|
||||
static checkSignature() {
|
||||
const storedSig = window.localStorage.getItem(PatcherCache.#KEY_SIGNATURE) || 0;
|
||||
const currentSig = PatcherCache.#getSignature();
|
||||
|
||||
if (currentSig !== parseInt(storedSig as string)) {
|
||||
BxLogger.warning(LOG_TAG, 'Signature changed');
|
||||
|
||||
// Clear cache
|
||||
window.localStorage.setItem(PatcherCache.#KEY_CACHE, '{}');
|
||||
// Save new signature
|
||||
window.localStorage.setItem(PatcherCache.#KEY_SIGNATURE, currentSig.toString());
|
||||
|
||||
// Refresh page
|
||||
// @ts-ignore
|
||||
window.location.reload(true);
|
||||
|
||||
} else {
|
||||
BxLogger.info(LOG_TAG, 'Signature unchanged');
|
||||
}
|
||||
}
|
||||
|
||||
static #cleanupPatches(patches: PatchArray): PatchArray {
|
||||
return patches.filter(item => {
|
||||
for (const id in PatcherCache.#CACHE) {
|
||||
const cached = PatcherCache.#CACHE[id];
|
||||
|
||||
if (cached.includes(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static getPatches(id: string): PatchArray {
|
||||
return PatcherCache.#CACHE[id];
|
||||
}
|
||||
|
||||
static saveToCache(subCache: { [key: string]: string[] }) {
|
||||
for (const id in subCache) {
|
||||
const patchNames = subCache[id];
|
||||
|
||||
let data = PatcherCache.#CACHE[id];
|
||||
if (!data) {
|
||||
PatcherCache.#CACHE[id] = patchNames;
|
||||
} else {
|
||||
for (const patchName of patchNames) {
|
||||
if (!data.includes(patchName)) {
|
||||
data.push(patchName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save to storage
|
||||
window.localStorage.setItem(PatcherCache.#KEY_CACHE, JSON.stringify(PatcherCache.#CACHE));
|
||||
}
|
||||
|
||||
static init() {
|
||||
// Read cache from storage
|
||||
PatcherCache.#CACHE = JSON.parse(window.localStorage.getItem(PatcherCache.#KEY_CACHE) || '{}');
|
||||
BxLogger.info(LOG_TAG, PatcherCache.#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 = PatcherCache.#cleanupPatches(PATCH_ORDERS);
|
||||
PLAYING_PATCH_ORDERS = PatcherCache.#cleanupPatches(PLAYING_PATCH_ORDERS);
|
||||
|
||||
BxLogger.info(LOG_TAG, PATCH_ORDERS.slice(0));
|
||||
BxLogger.info(LOG_TAG, PLAYING_PATCH_ORDERS.slice(0));
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('readystatechange', e => {
|
||||
if (document.readyState === 'interactive') {
|
||||
PatcherCache.checkSignature();
|
||||
}
|
||||
});
|
||||
|
||||
PatcherCache.init();
|
||||
|
@ -2,6 +2,9 @@ import { PrefKey, getPref, setPref } from "@utils/preferences";
|
||||
import { SCRIPT_VERSION } from "@utils/global";
|
||||
import { UserAgent } from "@utils/user-agent";
|
||||
|
||||
/**
|
||||
* Check for update
|
||||
*/
|
||||
export function checkForUpdate() {
|
||||
const CHECK_INTERVAL_SECONDS = 2 * 3600; // check every 2 hours
|
||||
|
||||
@ -25,6 +28,9 @@ export function checkForUpdate() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disable PWA requirement on Safari
|
||||
*/
|
||||
export function disablePwa() {
|
||||
const userAgent = ((window.navigator as any).orgUserAgent || window.navigator.userAgent || '').toLowerCase();
|
||||
if (!userAgent) {
|
||||
@ -39,3 +45,19 @@ export function disablePwa() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate hash code from a string
|
||||
* @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
|
||||
*/
|
||||
export function hashCode(str: string): number {
|
||||
let hash = 0;
|
||||
for (let i = 0, len = str.length; i < len; i++) {
|
||||
const chr = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + chr;
|
||||
hash |= 0; // Convert to 32-bit integer
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user