mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-28 10:21:44 +02:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
9f5f7b9d2e | |||
d04742bc25 | |||
ed871bbe83 | |||
dca8ab9cf6 | |||
1bf2f41813 | |||
0fb3b7b7f7 | |||
7709cceff0 | |||
f8b8012f5c | |||
1d8517a997 | |||
c893bb2a5d | |||
46469e3949 | |||
d8a085d43f | |||
b84c464066 | |||
f0549b388a | |||
9c3b1bd908 | |||
d671be21ee | |||
11aefb34d1 | |||
597cc9782d | |||
61cfd3f8db | |||
a3d5d6a819 |
179
dist/better-xcloud.lite.user.js
vendored
179
dist/better-xcloud.lite.user.js
vendored
File diff suppressed because one or more lines are too long
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 6.0.3
|
||||
// @version 6.0.4
|
||||
// ==/UserScript==
|
||||
|
237
dist/better-xcloud.user.js
vendored
237
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -7,11 +7,11 @@
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
html[data-xds-platform=tv] & {
|
||||
body[data-media-type=tv] & {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
html:not([data-xds-platform=tv]) & {
|
||||
body:not([data-media-type=tv]) & {
|
||||
flex-direction: row;
|
||||
|
||||
> button:first-of-type {
|
||||
@ -34,7 +34,7 @@
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
|
||||
html[data-xds-platform=tv] & {
|
||||
body[data-media-type=tv] & {
|
||||
flex-direction: column;
|
||||
|
||||
button {
|
||||
@ -42,7 +42,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
html:not([data-xds-platform=tv]) & {
|
||||
body:not([data-media-type=tv]) & {
|
||||
button {
|
||||
span {
|
||||
display: none;
|
||||
|
@ -140,8 +140,28 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
|
||||
font-family: var(--bx-normal-font) !important;
|
||||
}
|
||||
|
||||
select[multiple] {
|
||||
select[multiple], select[multiple]:focus {
|
||||
overflow: auto;
|
||||
border: none;
|
||||
|
||||
option {
|
||||
padding: 4px 6px;
|
||||
|
||||
&:checked {
|
||||
color = #1a7bc0;
|
||||
background: color linear-gradient(0deg, color 0%, color 100%);
|
||||
|
||||
&::before {
|
||||
content: '☑️';
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
height: 100%;
|
||||
line-height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide UI elements */
|
||||
|
@ -324,18 +324,22 @@
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #003861;
|
||||
height: 45px;
|
||||
align-items: center;
|
||||
|
||||
label {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
align-content: center;
|
||||
padding: 0 10px;
|
||||
background: #004f87;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
align-self: center;
|
||||
padding: 10px;
|
||||
width: 40px;
|
||||
width: 45px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ div[class^=StreamMenu-module__container] .bx-badges {
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
&[data-transparent=true] {
|
||||
&[data-shadow=true] {
|
||||
background: none;
|
||||
filter: drop-shadow(1px 0 0 #000000f0) drop-shadow(-1px 0 0 #000000f0) drop-shadow(0 1px 0 #000000f0) drop-shadow(0 -1px 0 #000000f0);
|
||||
}
|
||||
@ -165,10 +165,10 @@ div[class^=StreamMenu-module__container] .bx-badges {
|
||||
}
|
||||
|
||||
span {
|
||||
min-width: 60px;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
white-space: pre;
|
||||
|
||||
&[data-grade=good] {
|
||||
color: #6bffff;
|
||||
@ -181,9 +181,5 @@ div[class^=StreamMenu-module__container] .bx-badges {
|
||||
&[data-grade=bad] {
|
||||
color: #ff5f5f;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
min-width: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ export const enum PrefKey {
|
||||
SCREENSHOT_APPLY_FILTERS = 'screenshot.applyFilters',
|
||||
|
||||
BLOCK_TRACKING = 'block.tracking',
|
||||
BLOCK_SOCIAL_FEATURES = 'block.social',
|
||||
BLOCK_FEATURES = 'block.features',
|
||||
|
||||
LOADING_SCREEN_GAME_ART = 'loadingScreen.gameArt.show',
|
||||
LOADING_SCREEN_SHOW_WAIT_TIME = 'loadingScreen.waitTime.show',
|
||||
@ -73,7 +73,6 @@ export const enum PrefKey {
|
||||
UI_LAYOUT = 'ui.layout',
|
||||
UI_SCROLLBAR_HIDE = 'ui.hideScrollbar',
|
||||
UI_HIDE_SECTIONS = 'ui.hideSections',
|
||||
BYOG_DISABLED = 'feature.byog.disabled',
|
||||
|
||||
UI_GAME_CARD_SHOW_WAIT_TIME = 'ui.gameCard.waitTime.show',
|
||||
UI_SIMPLIFY_STREAM_MENU = 'ui.streamMenu.simplify',
|
||||
@ -104,8 +103,8 @@ export const enum PrefKey {
|
||||
STATS_QUICK_GLANCE_ENABLED = 'stats.quickGlance.enabled',
|
||||
STATS_POSITION = 'stats.position',
|
||||
STATS_TEXT_SIZE = 'stats.textSize',
|
||||
STATS_TRANSPARENT = 'stats.transparent',
|
||||
STATS_OPACITY = 'stats.opacity',
|
||||
STATS_OPACITY_ALL = 'stats.opacity.all',
|
||||
STATS_OPACITY_BACKGROUND = 'stats.opacity.background',
|
||||
STATS_CONDITIONAL_FORMATTING = 'stats.colors',
|
||||
|
||||
REMOTE_PLAY_ENABLED = 'xhome.enabled',
|
||||
|
@ -110,3 +110,11 @@ export const enum StreamVideoProcessing {
|
||||
USM = 'usm',
|
||||
CAS = 'cas',
|
||||
}
|
||||
|
||||
export const enum BlockFeature {
|
||||
CHAT = 'chat',
|
||||
FRIENDS = 'friends',
|
||||
BYOG = 'byog',
|
||||
NOTIFICATIONS_INVITES = 'notifications-invites',
|
||||
NOTIFICATIONS_ACHIEVEMENTS = 'notifications-achievements',
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ import { ScreenshotManager } from "./utils/screenshot-manager";
|
||||
import { NativeMkbHandler } from "./modules/mkb/native-mkb-handler";
|
||||
import { GuideMenu } from "./modules/ui/guide-menu";
|
||||
import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
|
||||
import { NativeMkbMode, TouchControllerMode, UiSection } from "./enums/pref-values";
|
||||
import { BlockFeature, NativeMkbMode, TouchControllerMode, UiSection } from "./enums/pref-values";
|
||||
import { HeaderSection } from "./modules/ui/header";
|
||||
import { GameTile } from "./modules/ui/game-tile";
|
||||
import { ProductDetailsPage } from "./modules/ui/product-details";
|
||||
@ -171,7 +171,7 @@ document.addEventListener('readystatechange', e => {
|
||||
}
|
||||
|
||||
// Hide "Play with Friends" skeleton section
|
||||
if (getPref<UiSection[]>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS)) {
|
||||
if (getPref<UiSection[]>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS) || getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES).includes(BlockFeature.FRIENDS)) {
|
||||
const $parent = document.querySelector('div[class*=PlayWithFriendsSkeleton]')?.closest<HTMLElement>('div[class*=HomePage-module]');
|
||||
$parent && ($parent.style.display = 'none');
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
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";
|
||||
import { blockSomeNotifications, hashCode, renderString } from "@utils/utils";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
|
||||
import codeControllerShortcuts from "./patches/controller-shortcuts.js" with { type: "text" };
|
||||
@ -10,12 +10,11 @@ import codeLocalCoOpEnable from "./patches/local-co-op-enable.js" with { type: "
|
||||
import codeRemotePlayEnable from "./patches/remote-play-enable.js" with { type: "text" };
|
||||
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
|
||||
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
|
||||
import { FeatureGates } from "@/utils/feature-gates.js";
|
||||
import { PrefKey, StorageKey } from "@/enums/pref-keys.js";
|
||||
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 { BlockFeature, NativeMkbMode, TouchControllerMode, UiLayout, UiSection } from "@/enums/pref-values";
|
||||
import { PatcherUtils } from "./patcher-utils.js";
|
||||
|
||||
export type PatchName = keyof typeof PATCHES;
|
||||
@ -244,24 +243,6 @@ logFunc(logTag, '//', logMessage);
|
||||
return str;
|
||||
},
|
||||
|
||||
// Override website's settings
|
||||
overrideSettings(str: string) {
|
||||
const index = str.indexOf(',EnableStreamGate:');
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the next "},"
|
||||
const endIndex = str.indexOf('},', index);
|
||||
|
||||
let newSettings = JSON.stringify(FeatureGates);
|
||||
newSettings = newSettings.substring(1, newSettings.length - 1);
|
||||
|
||||
const newCode = ',' + newSettings;
|
||||
str = PatcherUtils.insertAt(str, endIndex, newCode);
|
||||
return str;
|
||||
},
|
||||
|
||||
disableGamepadDisconnectedScreen(str: string) {
|
||||
const index = str.indexOf('"GamepadDisconnected_Title",');
|
||||
if (index < 0) {
|
||||
@ -713,6 +694,18 @@ true` + text;
|
||||
return str;
|
||||
},
|
||||
|
||||
// Don't render News section
|
||||
ignoreNewsSection(str: string) {
|
||||
let index = str.indexOf('Logger("CarouselRow")');
|
||||
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'const ', index, 200));
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
str = PatcherUtils.insertAt(str, index, 'return null;');
|
||||
return str;
|
||||
},
|
||||
|
||||
// Don't render "Play With Friends" sections
|
||||
ignorePlayWithFriendsSection(str: string) {
|
||||
let index = str.indexOf('location:"PlayWithFriendsRow",');
|
||||
@ -954,7 +947,43 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
|
||||
|
||||
str = str.replace(text, text + 'return;');
|
||||
return str;
|
||||
}
|
||||
},
|
||||
|
||||
changeNotificationsSubscription(str: string) {
|
||||
let text = ';buildSubscriptionQueryParamsForNotifications(';
|
||||
let index = str.indexOf(text);
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
index += text.length;
|
||||
// Get parameter name
|
||||
const subsVar = str[index];
|
||||
|
||||
// Find index after {
|
||||
index = str.indexOf('{', index) + 1;
|
||||
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||
const filters = [];
|
||||
if (blockFeatures.includes(BlockFeature.NOTIFICATIONS_INVITES)) {
|
||||
filters.push('GameInvite', 'PartyInvite');
|
||||
}
|
||||
|
||||
if (blockFeatures.includes(BlockFeature.FRIENDS)) {
|
||||
filters.push('Follower');
|
||||
}
|
||||
|
||||
if (blockFeatures.includes(BlockFeature.NOTIFICATIONS_ACHIEVEMENTS)) {
|
||||
filters.push('AchievementUnlock');
|
||||
}
|
||||
|
||||
const newCode = `
|
||||
let subs = ${subsVar};
|
||||
subs = subs.filter(val => !${JSON.stringify(filters)}.includes(val));
|
||||
${subsVar} = subs;
|
||||
`;
|
||||
str = PatcherUtils.insertAt(str, index, newCode);
|
||||
return str;
|
||||
},
|
||||
};
|
||||
|
||||
let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||
@ -972,7 +1001,6 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||
'patchRequestInfoCrash',
|
||||
|
||||
'disableStreamGate',
|
||||
'overrideSettings',
|
||||
'broadcastPollingMode',
|
||||
'patchGamepadPolling',
|
||||
|
||||
@ -1021,13 +1049,19 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||
'enableConsoleLogging',
|
||||
'enableXcloudLogger',
|
||||
] : []),
|
||||
|
||||
...(blockSomeNotifications() ? [
|
||||
'changeNotificationsSubscription',
|
||||
] : []),
|
||||
]);
|
||||
|
||||
const hideSections = getPref<UiSection[]>(PrefKey.UI_HIDE_SECTIONS);
|
||||
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',
|
||||
hideSections.includes(UiSection.NEWS) && 'ignoreNewsSection',
|
||||
hideSections.includes(UiSection.FRIENDS) && 'ignorePlayWithFriendsSection',
|
||||
hideSections.includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
|
||||
STATES.browser.capabilities.touch && hideSections.includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
|
||||
hideSections.some(value => [UiSection.NATIVE_MKB, UiSection.MOST_POPULAR].includes(value)) && 'ignoreSiglSections',
|
||||
]);
|
||||
|
||||
// Only when playing
|
||||
|
@ -253,8 +253,9 @@ export class StreamBadges {
|
||||
const allAudioCodecs: Record<string, RTCBasicStat> = {};
|
||||
let audioCodecId;
|
||||
|
||||
const allCandidates: Record<string, string> = {};
|
||||
let candidateId;
|
||||
const allCandidatePairs: Record<string, string> = {};
|
||||
const allRemoteCandidates: Record<string, string> = {};
|
||||
let candidatePairId;
|
||||
|
||||
stats.forEach((stat: RTCBasicStat) => {
|
||||
if (stat.type === 'codec') {
|
||||
@ -275,10 +276,12 @@ export class StreamBadges {
|
||||
} else if (stat.kind === 'audio') {
|
||||
audioCodecId = stat.codecId;
|
||||
}
|
||||
} else if (stat.type === 'candidate-pair' && stat.packetsReceived > 0 && stat.state === 'succeeded') {
|
||||
candidateId = stat.remoteCandidateId;
|
||||
} else if (stat.type === 'transport' && (stat as unknown as RTCTransportStats).selectedCandidatePairId) {
|
||||
candidatePairId = (stat as unknown as RTCTransportStats).selectedCandidatePairId;
|
||||
} else if (stat.type === 'candidate-pair') {
|
||||
allCandidatePairs[stat.id] = (stat as unknown as RTCIceCandidatePairStats).remoteCandidateId;
|
||||
} else if (stat.type === 'remote-candidate') {
|
||||
allCandidates[stat.id] = stat.address;
|
||||
allRemoteCandidates[stat.id] = stat.address;
|
||||
}
|
||||
});
|
||||
|
||||
@ -336,12 +339,12 @@ export class StreamBadges {
|
||||
}
|
||||
|
||||
// Get server type
|
||||
if (candidateId) {
|
||||
BxLogger.info('candidate', candidateId, allCandidates);
|
||||
if (candidatePairId) {
|
||||
BxLogger.info('candidate', candidatePairId, allCandidatePairs);
|
||||
|
||||
// Server + Region
|
||||
let text = '';
|
||||
const isIpv6 = allCandidates[candidateId].includes(':');
|
||||
const isIpv6 = allRemoteCandidates[allCandidatePairs[candidatePairId]].includes(':');
|
||||
|
||||
const server = this.serverInfo.server;
|
||||
if (server && server.region) {
|
||||
|
@ -193,12 +193,21 @@ export class StreamStats {
|
||||
|
||||
refreshStyles() {
|
||||
const PREF_ITEMS = getPref<StreamStat[]>(PrefKey.STATS_ITEMS);
|
||||
const PREF_OPACITY_BG = getPref<number>(PrefKey.STATS_OPACITY_BACKGROUND);
|
||||
|
||||
const $container = this.$container;
|
||||
$container.dataset.stats = '[' + PREF_ITEMS.join('][') + ']';
|
||||
$container.dataset.position = getPref(PrefKey.STATS_POSITION);
|
||||
$container.dataset.transparent = getPref(PrefKey.STATS_TRANSPARENT);
|
||||
$container.style.opacity = getPref(PrefKey.STATS_OPACITY) + '%';
|
||||
|
||||
if (PREF_OPACITY_BG === 0) {
|
||||
$container.style.removeProperty('background-color');
|
||||
$container.dataset.shadow = 'true';
|
||||
} else {
|
||||
delete $container.dataset.shadow;
|
||||
$container.style.backgroundColor = `rgba(0, 0, 0, ${PREF_OPACITY_BG}%)`;
|
||||
}
|
||||
|
||||
$container.style.opacity = getPref(PrefKey.STATS_OPACITY_ALL) + '%';
|
||||
$container.style.fontSize = getPref(PrefKey.STATS_TEXT_SIZE);
|
||||
}
|
||||
|
||||
|
@ -282,12 +282,14 @@ export class SettingsDialog extends NavigationDialog {
|
||||
PrefKey.UI_HIDE_SYSTEM_MENU_ICON,
|
||||
PrefKey.UI_DISABLE_FEEDBACK_DIALOG,
|
||||
PrefKey.UI_REDUCE_ANIMATIONS,
|
||||
PrefKey.BLOCK_SOCIAL_FEATURES,
|
||||
PrefKey.BYOG_DISABLED,
|
||||
{
|
||||
pref: PrefKey.UI_HIDE_SECTIONS,
|
||||
multiLines: true,
|
||||
},
|
||||
{
|
||||
pref: PrefKey.BLOCK_FEATURES,
|
||||
multiLines: true,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
requiredVariants: 'full',
|
||||
@ -628,10 +630,10 @@ export class SettingsDialog extends NavigationDialog {
|
||||
pref: PrefKey.STATS_TEXT_SIZE,
|
||||
onChange: StreamStats.refreshStyles,
|
||||
}, {
|
||||
pref: PrefKey.STATS_OPACITY,
|
||||
pref: PrefKey.STATS_OPACITY_ALL,
|
||||
onChange: StreamStats.refreshStyles,
|
||||
}, {
|
||||
pref: PrefKey.STATS_TRANSPARENT,
|
||||
pref: PrefKey.STATS_OPACITY_BACKGROUND,
|
||||
onChange: StreamStats.refreshStyles,
|
||||
}, {
|
||||
pref: PrefKey.STATS_CONDITIONAL_FORMATTING,
|
||||
|
@ -12,6 +12,7 @@ import { TouchController } from "@/modules/touch-controller";
|
||||
import { NativeMkbMode, TouchControllerMode } from "@/enums/pref-values";
|
||||
import { Patcher, type PatchPage } from "@/modules/patcher/patcher";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
import { FeatureGates } from "./feature-gates";
|
||||
|
||||
export enum SupportedInputType {
|
||||
CONTROLLER = 'Controller',
|
||||
@ -36,6 +37,15 @@ export const BxExposed = {
|
||||
BxLogger.error(LOG_TAG, e);
|
||||
}
|
||||
|
||||
// Override feature gates
|
||||
try {
|
||||
for (const exp in FeatureGates) {
|
||||
state.experiments.overrideFeatureGates[exp.toLocaleLowerCase()] = FeatureGates[exp];
|
||||
}
|
||||
} catch (e) {
|
||||
BxLogger.error(LOG_TAG, e);
|
||||
}
|
||||
|
||||
// Add list of games with custom layouts to the official list
|
||||
try {
|
||||
const sigls = state.xcloud.sigls;
|
||||
|
@ -7,12 +7,12 @@ const enum TextColor {
|
||||
}
|
||||
|
||||
export class BxLogger {
|
||||
static info = (tag: string, ...args: any[]) => BxLogger.log(TextColor.INFO, tag, ...args);
|
||||
static warning = (tag: string, ...args: any[]) => BxLogger.log(TextColor.WARNING, tag, ...args);
|
||||
static info = (tag: string, ...args: any[]) => BX_FLAGS.Debug && BxLogger.log(TextColor.INFO, tag, ...args);
|
||||
static warning = (tag: string, ...args: any[]) => BX_FLAGS.Debug && BxLogger.log(TextColor.WARNING, tag, ...args);
|
||||
static error = (tag: string, ...args: any[]) => BxLogger.log(TextColor.ERROR, tag, ...args);
|
||||
|
||||
private static log(color: string, tag: string, ...args: any) {
|
||||
BX_FLAGS.Debug && console.log(`%c[BxC]`, `color:${color};font-weight:bold;`, tag, '//', ...args);
|
||||
console.log(`%c[BxC]`, `color:${color};font-weight:bold;`, tag, '//', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { CE } from "@utils/html";
|
||||
import { compressCss, renderStylus } from "@macros/build" with { type: "macro" };
|
||||
import { UiSection } from "@/enums/pref-values";
|
||||
import { BlockFeature, UiSection } from "@/enums/pref-values";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
@ -18,7 +18,7 @@ export function addCss() {
|
||||
}
|
||||
|
||||
// Hide BYOG section
|
||||
if (getPref(PrefKey.BYOG_DISABLED)) {
|
||||
if (getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES).includes(BlockFeature.BYOG)) {
|
||||
selectorToHide.push('#BodyContent > div[class*=ByogRow-module__container___]');
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ export function addCss() {
|
||||
}
|
||||
|
||||
// Hide "Start a party" button in the Guide menu
|
||||
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
|
||||
if (getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES).includes(BlockFeature.FRIENDS)) {
|
||||
selectorToHide.push('#gamepass-dialog-root div[class^=AchievementsPreview-module__container] + button[class*=HomeLandingPage-module__button]');
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import { NativeMkbMode } from "@/enums/pref-values";
|
||||
import { BlockFeature, NativeMkbMode } from "@/enums/pref-values";
|
||||
|
||||
export let FeatureGates: { [key: string]: boolean } = {
|
||||
PwaPrompt: false,
|
||||
@ -17,12 +17,17 @@ if (nativeMkbMode !== NativeMkbMode.DEFAULT) {
|
||||
}
|
||||
|
||||
// Disable chat feature
|
||||
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
|
||||
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||
if (blockFeatures.includes(BlockFeature.CHAT)) {
|
||||
FeatureGates.EnableGuideChatTab = false;
|
||||
}
|
||||
|
||||
if (blockFeatures.includes(BlockFeature.FRIENDS)) {
|
||||
FeatureGates.EnableFriendsAndFollowers = false;
|
||||
}
|
||||
|
||||
// Disable BYOG feature
|
||||
if (getPref(PrefKey.BYOG_DISABLED)) {
|
||||
if (blockFeatures.includes(BlockFeature.BYOG)) {
|
||||
FeatureGates.EnableBYOG = false;
|
||||
FeatureGates.EnableBYOGPurchase = false;
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ import { XcloudInterceptor } from "./xcloud-interceptor";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import type { RemotePlayConsoleAddresses } from "@/types/network";
|
||||
import { StreamResolution } from "@/enums/pref-values";
|
||||
import { BlockFeature, StreamResolution } from "@/enums/pref-values";
|
||||
import { blockAllNotifications } from "./utils";
|
||||
|
||||
type RequestType = 'xcloud' | 'xhome';
|
||||
|
||||
@ -128,23 +129,37 @@ export function interceptHttpRequests() {
|
||||
// Clear Applications Insight buffers
|
||||
clearAllLogs();
|
||||
|
||||
BLOCKED_URLS = BLOCKED_URLS.concat([
|
||||
BLOCKED_URLS.push(
|
||||
'https://arc.msn.com',
|
||||
'https://browser.events.data.microsoft.com',
|
||||
'https://dc.services.visualstudio.com',
|
||||
'https://2c06dea3f26c40c69b8456d319791fd0@o427368.ingest.sentry.io',
|
||||
'https://mscom.demdex.net',
|
||||
]);
|
||||
);
|
||||
}
|
||||
|
||||
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
|
||||
BLOCKED_URLS = BLOCKED_URLS.concat([
|
||||
|
||||
// 'https://notificationinbox.xboxlive.com',
|
||||
// 'https://accounts.xboxlive.com/family/memberXuid',
|
||||
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||
if (blockFeatures.includes(BlockFeature.CHAT)) {
|
||||
BLOCKED_URLS.push(
|
||||
'https://xblmessaging.xboxlive.com/network/xbox/users/me/inbox',
|
||||
);
|
||||
}
|
||||
|
||||
if (blockFeatures.includes(BlockFeature.FRIENDS)) {
|
||||
BLOCKED_URLS.push(
|
||||
'https://peoplehub.xboxlive.com/users/me/people/social',
|
||||
'https://peoplehub.xboxlive.com/users/me/people/recommendations',
|
||||
'https://xblmessaging.xboxlive.com/network/xbox/users/me/inbox',
|
||||
// 'https://notificationinbox.xboxlive.com',
|
||||
// 'https://accounts.xboxlive.com/family/memberXuid',
|
||||
]);
|
||||
);
|
||||
}
|
||||
|
||||
// Block all notifications
|
||||
if (blockAllNotifications()) {
|
||||
BLOCKED_URLS.push(
|
||||
'https://notificationinbox.xboxlive.com/',
|
||||
);
|
||||
}
|
||||
|
||||
const xhrPrototype = XMLHttpRequest.prototype;
|
||||
@ -159,11 +174,13 @@ export function interceptHttpRequests() {
|
||||
};
|
||||
|
||||
xhrPrototype.send = function(...arg) {
|
||||
for (const blocked of BLOCKED_URLS) {
|
||||
if ((this as any)._url.startsWith(blocked)) {
|
||||
if (blocked === 'https://dc.services.visualstudio.com') {
|
||||
for (const url of BLOCKED_URLS) {
|
||||
if ((this as any)._url.startsWith(url)) {
|
||||
if (url === 'https://dc.services.visualstudio.com') {
|
||||
window.setTimeout(clearAllLogs, 1000);
|
||||
}
|
||||
|
||||
BxLogger.warning('Blocked URL', url);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -195,6 +212,7 @@ export function interceptHttpRequests() {
|
||||
// Check blocked URLs
|
||||
for (const blocked of BLOCKED_URLS) {
|
||||
if (url.startsWith(blocked)) {
|
||||
BxLogger.warning('Blocked URL', url);
|
||||
return new Response('{"acc":1,"webResult":{}}', {
|
||||
status: 200,
|
||||
statusText: '200 OK',
|
||||
|
@ -8,7 +8,7 @@ import { CE } from "../html";
|
||||
import { t, SUPPORTED_LANGUAGES } from "../translation";
|
||||
import { UserAgent } from "../user-agent";
|
||||
import { BaseSettingsStore as BaseSettingsStorage } from "./base-settings-storage";
|
||||
import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerStyleStandard, TouchControllerStyleCustom, GameBarPosition, DeviceVibrationMode, NativeMkbMode, UiLayout, UiSection, StreamPlayerType, StreamVideoProcessing, VideoRatio, StreamStat, VideoPosition } from "@/enums/pref-values";
|
||||
import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerStyleStandard, TouchControllerStyleCustom, GameBarPosition, DeviceVibrationMode, NativeMkbMode, UiLayout, UiSection, StreamPlayerType, StreamVideoProcessing, VideoRatio, StreamStat, VideoPosition, BlockFeature } from "@/enums/pref-values";
|
||||
import { MkbMappingDefaultPresetId } from "../local-db/mkb-mapping-presets-table";
|
||||
import { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table";
|
||||
import { GhPagesUtils } from "../gh-pages";
|
||||
@ -321,10 +321,14 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
requiredVariants: 'full',
|
||||
label: t('enable-local-co-op-support'),
|
||||
default: false,
|
||||
note: () => CE<HTMLAnchorElement>('a', {
|
||||
href: 'https://github.com/redphx/better-xcloud/discussions/275',
|
||||
target: '_blank',
|
||||
}, t('enable-local-co-op-support-note')),
|
||||
note: () => CE('div', {},
|
||||
CE<HTMLAnchorElement>('a', {
|
||||
href: 'https://github.com/redphx/better-xcloud/discussions/275',
|
||||
target: '_blank',
|
||||
}, t('enable-local-co-op-support-note')),
|
||||
CE('br'),
|
||||
'⚠️ ' + t('unexpected-behavior'),
|
||||
),
|
||||
},
|
||||
|
||||
[PrefKey.UI_CONTROLLER_SHOW_STATUS]: {
|
||||
@ -582,25 +586,29 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
},
|
||||
},
|
||||
|
||||
[PrefKey.BYOG_DISABLED]: {
|
||||
label: t('disable-byog-feature'),
|
||||
default: false,
|
||||
},
|
||||
|
||||
[PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME]: {
|
||||
requiredVariants: 'full',
|
||||
label: t('show-wait-time-in-game-card'),
|
||||
default: true,
|
||||
},
|
||||
|
||||
[PrefKey.BLOCK_SOCIAL_FEATURES]: {
|
||||
label: t('disable-social-features'),
|
||||
default: false,
|
||||
},
|
||||
[PrefKey.BLOCK_TRACKING]: {
|
||||
label: t('disable-xcloud-analytics'),
|
||||
default: false,
|
||||
},
|
||||
[PrefKey.BLOCK_FEATURES]: {
|
||||
label: t('disable-features'),
|
||||
default: [],
|
||||
multipleOptions: {
|
||||
[BlockFeature.CHAT]: t('chat'),
|
||||
[BlockFeature.FRIENDS]: t('friends-followers'),
|
||||
[BlockFeature.BYOG]: t('stream-your-own-game'),
|
||||
[BlockFeature.NOTIFICATIONS_INVITES]: t('notifications') + ': ' + t('invites'),
|
||||
[BlockFeature.NOTIFICATIONS_ACHIEVEMENTS]: t('notifications') + ': ' + t('achievements'),
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
[PrefKey.USER_AGENT_PROFILE]: {
|
||||
label: t('user-agent-profile'),
|
||||
note: '⚠️ ' + t('unexpected-behavior'),
|
||||
@ -821,11 +829,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
'1.1rem': t('large'),
|
||||
},
|
||||
},
|
||||
[PrefKey.STATS_TRANSPARENT]: {
|
||||
label: t('transparent-background'),
|
||||
default: false,
|
||||
},
|
||||
[PrefKey.STATS_OPACITY]: {
|
||||
[PrefKey.STATS_OPACITY_ALL]: {
|
||||
label: t('opacity'),
|
||||
default: 80,
|
||||
min: 50,
|
||||
@ -836,6 +840,17 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
ticks: 10,
|
||||
},
|
||||
},
|
||||
[PrefKey.STATS_OPACITY_BACKGROUND]: {
|
||||
label: t('background-opacity'),
|
||||
default: 100,
|
||||
min: 0,
|
||||
max: 100,
|
||||
params: {
|
||||
steps: 10,
|
||||
suffix: '%',
|
||||
ticks: 10,
|
||||
},
|
||||
},
|
||||
[PrefKey.STATS_CONDITIONAL_FORMATTING]: {
|
||||
label: t('conditional-formatting'),
|
||||
default: false,
|
||||
|
@ -96,7 +96,7 @@ export class StreamStatsCollector {
|
||||
current: -1,
|
||||
grades: [40, 75, 100],
|
||||
toString() {
|
||||
return this.current === -1 ? '???' : this.current.toString();
|
||||
return this.current === -1 ? '???' : this.current.toString().padStart(3, ' ');
|
||||
},
|
||||
},
|
||||
|
||||
@ -104,7 +104,7 @@ export class StreamStatsCollector {
|
||||
current: 0,
|
||||
grades: [30, 40, 60],
|
||||
toString() {
|
||||
return `${this.current.toFixed(1)}ms`;
|
||||
return `${this.current.toFixed(1)}ms`.padStart(6, ' ');
|
||||
},
|
||||
},
|
||||
|
||||
@ -112,14 +112,14 @@ export class StreamStatsCollector {
|
||||
current: 0,
|
||||
toString() {
|
||||
const maxFps = getPref<VideoMaxFps>(PrefKey.VIDEO_MAX_FPS);
|
||||
return maxFps < 60 ? `${maxFps}/${this.current}` : this.current.toString();
|
||||
return maxFps < 60 ? `${maxFps}/${this.current}`.padStart(5, ' ') : this.current.toString();
|
||||
},
|
||||
},
|
||||
|
||||
[StreamStat.BITRATE]: {
|
||||
current: 0,
|
||||
toString() {
|
||||
return `${this.current.toFixed(1)} Mbps`;
|
||||
return `${this.current.toFixed(1)} Mbps`.padStart(9, ' ');
|
||||
},
|
||||
},
|
||||
|
||||
@ -128,7 +128,7 @@ export class StreamStatsCollector {
|
||||
dropped: 0,
|
||||
toString() {
|
||||
const framesDroppedPercentage = (this.dropped * 100 / ((this.dropped + this.received) || 1)).toFixed(1);
|
||||
return framesDroppedPercentage === '0.00' ? this.dropped.toString() : `${this.dropped} (${framesDroppedPercentage}%)`;
|
||||
return framesDroppedPercentage === '0.0' ? this.dropped.toString() : `${this.dropped} (${framesDroppedPercentage}%)`;
|
||||
},
|
||||
},
|
||||
|
||||
@ -137,7 +137,7 @@ export class StreamStatsCollector {
|
||||
dropped: 0,
|
||||
toString() {
|
||||
const packetsLostPercentage = (this.dropped * 100 / ((this.dropped + this.received) || 1)).toFixed(1);
|
||||
return packetsLostPercentage === '0.00' ? this.dropped.toString() : `${this.dropped} (${packetsLostPercentage}%)`;
|
||||
return packetsLostPercentage === '0.0' ? this.dropped.toString() : `${this.dropped} (${packetsLostPercentage}%)`;
|
||||
},
|
||||
},
|
||||
|
||||
@ -146,14 +146,14 @@ export class StreamStatsCollector {
|
||||
total: 0,
|
||||
grades: [6, 9, 12],
|
||||
toString() {
|
||||
return isNaN(this.current) ? '??ms' : `${this.current.toFixed(1)}ms`;
|
||||
return isNaN(this.current) ? '??ms' : `${this.current.toFixed(1)}ms`.padStart(6, ' ');
|
||||
},
|
||||
},
|
||||
|
||||
[StreamStat.DOWNLOAD]: {
|
||||
total: 0,
|
||||
toString() {
|
||||
return humanFileSize(this.total);
|
||||
return humanFileSize(this.total).padStart(8, ' ');
|
||||
},
|
||||
},
|
||||
|
||||
@ -201,6 +201,7 @@ export class StreamStatsCollector {
|
||||
};
|
||||
|
||||
private lastVideoStat?: RTCInboundRtpStreamStats | null;
|
||||
private selectedCandidatePairId: string | null | undefined = null;
|
||||
|
||||
private constructor() {
|
||||
BxLogger.info(this.LOG_TAG, 'constructor()');
|
||||
@ -212,6 +213,22 @@ export class StreamStatsCollector {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find selected candidate
|
||||
if (!this.selectedCandidatePairId) {
|
||||
let found = false;
|
||||
stats.forEach(stat => {
|
||||
if (found || stat.type !== 'transport') {
|
||||
return;
|
||||
}
|
||||
|
||||
stat = (stat as unknown as RTCTransportStats);
|
||||
if (stat.iceState === 'connected' && stat.selectedCandidatePairId) {
|
||||
this.selectedCandidatePairId = (stat as unknown as RTCTransportStats).selectedCandidatePairId;
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stats.forEach(stat => {
|
||||
if (stat.type === 'inbound-rtp' && stat.kind === 'video') {
|
||||
// FPS
|
||||
@ -256,7 +273,7 @@ export class StreamStatsCollector {
|
||||
dt.current = dt.total / framesDecodedDiff * 1000;
|
||||
|
||||
this.lastVideoStat = stat;
|
||||
} else if (stat.type === 'candidate-pair' && stat.packetsReceived > 0 && stat.state === 'succeeded') {
|
||||
} else if (this.selectedCandidatePairId && stat.type === 'candidate-pair' && stat.id === this.selectedCandidatePairId) {
|
||||
// Round Trip Time
|
||||
const ping = this.currentStats[StreamStat.PING];
|
||||
ping.current = stat.currentRoundTripTime ? stat.currentRoundTripTime * 1000 : -1;
|
||||
|
@ -27,6 +27,7 @@ export const SUPPORTED_LANGUAGES = {
|
||||
};
|
||||
|
||||
const Texts = {
|
||||
"achievements": "Achievements",
|
||||
"activate": "Activate",
|
||||
"activated": "Activated",
|
||||
"active": "Active",
|
||||
@ -42,6 +43,7 @@ const Texts = {
|
||||
"auto": "Auto",
|
||||
"back-to-home": "Back to home",
|
||||
"back-to-home-confirm": "Do you want to go back to the home page (without disconnecting)?",
|
||||
"background-opacity": "Background opacity",
|
||||
"battery": "Battery",
|
||||
"battery-saving": "Battery saving",
|
||||
"better-xcloud": "Better xCloud",
|
||||
@ -60,6 +62,7 @@ const Texts = {
|
||||
"cancel": "Cancel",
|
||||
"cant-stream-xbox-360-games": "Can't stream Xbox 360 games",
|
||||
"center": "Center",
|
||||
"chat": "Chat",
|
||||
"clarity-boost": "Clarity boost",
|
||||
"clarity-boost-warning": "These settings don't work when the Clarity Boost mode is ON",
|
||||
"clear": "Clear",
|
||||
@ -84,6 +87,8 @@ const Texts = {
|
||||
"contrast": "Contrast",
|
||||
"controller": "Controller",
|
||||
"controller-friendly-ui": "Controller-friendly UI",
|
||||
"controller-mapping": "Controller mapping",
|
||||
"controller-mapping-in-game": "In-game controller mapping",
|
||||
"controller-shortcuts": "Controller shortcuts",
|
||||
"controller-shortcuts-connect-note": "Connect a controller to use this feature",
|
||||
"controller-shortcuts-in-game": "In-game controller shortcuts",
|
||||
@ -103,6 +108,7 @@ const Texts = {
|
||||
"device-vibration-not-using-gamepad": "On when not using gamepad",
|
||||
"disable": "Disable",
|
||||
"disable-byog-feature": "Disable \"Stream your own game\" feature",
|
||||
"disable-features": "Disable features",
|
||||
"disable-home-context-menu": "Disable context menu in Home page",
|
||||
"disable-post-stream-feedback-dialog": "Disable post-stream feedback dialog",
|
||||
"disable-social-features": "Disable social features",
|
||||
@ -127,6 +133,7 @@ const Texts = {
|
||||
"force-native-mkb-games": "Force native Mouse & Keyboard for these games",
|
||||
"fortnite-allow-stw-mode": "Allows playing \"Save the World\" mode on mobile",
|
||||
"fortnite-force-console-version": "Fortnite: force console version",
|
||||
"friends-followers": "Friends and followers",
|
||||
"game-bar": "Game Bar",
|
||||
"getting-consoles-list": "Getting the list of consoles...",
|
||||
"guide": "Guide",
|
||||
@ -148,6 +155,7 @@ const Texts = {
|
||||
"import": "Import",
|
||||
"increase": "Increase",
|
||||
"install-android": "Better xCloud app for Android",
|
||||
"invites": "Invites",
|
||||
"japan": "Japan",
|
||||
"jitter": "Jitter",
|
||||
"keyboard-key": "Keyboard key",
|
||||
@ -165,6 +173,7 @@ const Texts = {
|
||||
"lowest-quality": "Lowest quality",
|
||||
"manage": "Manage",
|
||||
"map-mouse-to": "Map mouse to",
|
||||
"mapping": "Mapping",
|
||||
"may-not-work-properly": "May not work properly!",
|
||||
"menu": "Menu",
|
||||
"microphone": "Microphone",
|
||||
@ -203,6 +212,7 @@ const Texts = {
|
||||
"no-consoles-found": "No consoles found",
|
||||
"no-controllers-connected": "No controllers connected",
|
||||
"normal": "Normal",
|
||||
"notifications": "Notifications",
|
||||
"off": "Off",
|
||||
"official": "Official",
|
||||
"on": "On",
|
||||
@ -328,6 +338,7 @@ const Texts = {
|
||||
"stream": "Stream",
|
||||
"stream-settings": "Stream settings",
|
||||
"stream-stats": "Stream stats",
|
||||
"stream-your-own-game": "Stream your own game",
|
||||
"stretch": "Stretch",
|
||||
"suggest-settings": "Suggest settings",
|
||||
"suggest-settings-link": "Suggest recommended settings for this device",
|
||||
|
@ -97,7 +97,7 @@ export class TrueAchievements {
|
||||
}
|
||||
this.updateIds(xboxTitleId);
|
||||
|
||||
if (document.documentElement.dataset.xdsPlatform === 'tv') {
|
||||
if (document.body.dataset.mediaType === 'tv') {
|
||||
$div.appendChild(this.$link);
|
||||
} else {
|
||||
$div.appendChild(this.$button);
|
||||
|
@ -5,6 +5,7 @@ import { Toast } from "./toast";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref, setPref } from "./settings-storages/global-settings-storage";
|
||||
import { LocalDb } from "./local-db/local-db";
|
||||
import { BlockFeature } from "@/enums/pref-values";
|
||||
|
||||
/**
|
||||
* Check for update
|
||||
@ -155,3 +156,19 @@ export function clearAllData() {
|
||||
|
||||
alert(t('clear-data-success'));
|
||||
}
|
||||
|
||||
export function blockAllNotifications() {
|
||||
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||
const blockAll = [BlockFeature.FRIENDS, BlockFeature.NOTIFICATIONS_ACHIEVEMENTS, BlockFeature.NOTIFICATIONS_INVITES].every(value => blockFeatures.includes(value));
|
||||
return blockAll;
|
||||
}
|
||||
|
||||
export function blockSomeNotifications() {
|
||||
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||
if (blockAllNotifications()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const blockSome = [BlockFeature.FRIENDS, BlockFeature.NOTIFICATIONS_ACHIEVEMENTS, BlockFeature.NOTIFICATIONS_INVITES].some(value => blockFeatures.includes(value));
|
||||
return blockSome;
|
||||
}
|
||||
|
@ -82,6 +82,7 @@ export class XcloudInterceptor {
|
||||
region.contintent = serverExtra[regionName][1];
|
||||
} else {
|
||||
region.contintent = 'other';
|
||||
BX_FLAGS.Debug && alert('New server: ' + shortName);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user