Compare commits

...

30 Commits

Author SHA1 Message Date
458928d615 Bump version to 6.0.6 2024-12-13 06:32:52 +07:00
20bf2b1ab6 Turn on "EnableTakControlResizing" flag 2024-12-13 06:32:32 +07:00
901f55c683 Show different colors for wait time 2024-12-13 06:23:58 +07:00
15bb18644f Fix wait time stopped showing in game tile (#597) 2024-12-13 05:56:02 +07:00
873f6546a4 Fix input slider not working with gamepad (#596) 2024-12-13 05:41:25 +07:00
1db7d4f8d7 Set unadjustedMovement for MKB 2024-12-12 21:37:41 +07:00
e0b04f306f Bump version to 6.0.5 2024-12-12 06:55:00 +07:00
a3c948b070 Fix problem with Smart TV profile and Guide menu (#594) 2024-12-12 06:53:25 +07:00
4e736175b4 Fix Bx button in Guide menu not working 2024-12-12 06:46:41 +07:00
cb66340177 Fix not showing Bx button in unsupported page 2024-12-12 06:35:07 +07:00
9f5f7b9d2e Bump version to 6.0.4 2024-12-11 20:37:10 +07:00
d04742bc25 Update translations 2024-12-11 20:36:51 +07:00
ed871bbe83 Update dists 2024-12-11 18:59:31 +07:00
dca8ab9cf6 Fix stats texts 2024-12-11 18:59:24 +07:00
1bf2f41813 Fix not getting the correct candidate pair 2024-12-11 18:55:28 +07:00
0fb3b7b7f7 Pad stats 2024-12-11 18:08:28 +07:00
7709cceff0 Add stat's background opacity 2024-12-11 17:50:04 +07:00
f8b8012f5c Add "ignoreNewsSection" patch 2024-12-11 17:21:20 +07:00
1d8517a997 Update <select multiple> CSS 2024-12-11 07:51:41 +07:00
c893bb2a5d Block notifications 2024-12-11 07:25:48 +07:00
46469e3949 Fix Guide CSS in TV layout 2024-12-11 06:12:15 +07:00
d8a085d43f Update suggestion's styles 2024-12-10 21:53:57 +07:00
b84c464066 Add "Disable features" setting 2024-12-10 21:30:21 +07:00
f0549b388a Update dists 2024-12-10 20:55:12 +07:00
9c3b1bd908 Change background color of selected options in <select multiple> 2024-12-10 20:54:53 +07:00
d671be21ee Also disable Friends feature when blocking social features 2024-12-10 20:53:43 +07:00
11aefb34d1 Fix overriding features not working 2024-12-10 20:51:27 +07:00
597cc9782d Always show error log 2024-12-10 20:51:00 +07:00
61cfd3f8db Alert new server 2024-12-10 20:50:42 +07:00
a3d5d6a819 Add note for local co-op feature 2024-12-10 20:50:27 +07:00
31 changed files with 600 additions and 305 deletions

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

View File

@ -7,11 +7,11 @@
margin-bottom: 0 !important;
}
html[data-xds-platform=tv] & {
body[data-bx-media-type=tv] & {
flex-direction: column;
}
html:not([data-xds-platform=tv]) & {
body:not([data-bx-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-bx-media-type=tv] & {
flex-direction: column;
button {
@ -42,7 +42,7 @@
}
}
html:not([data-xds-platform=tv]) & {
body:not([data-bx-media-type=tv]) & {
button {
span {
display: none;

View File

@ -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 */
@ -174,7 +194,7 @@ div[class*=SupportedInputsBadge] {
top: 0;
left: 0;
z-index: 1;
background: #0000008c;
background: rgba(0, 0, 0, 0.5);
display: flex;
border-radius: 4px 0 4px 0;
align-items: center;
@ -194,6 +214,18 @@ div[class*=SupportedInputsBadge] {
font-weight: bold;
margin-left: 2px;
}
&[data-duration=short] {
background-color: rgba(0, 133, 133, 0.75);
}
&[data-duration=medium] {
background-color: rgba(213, 133, 0, 0.75);
}
&[data-duration=long] {
background-color: rgba(150, 0, 0, 0.75);
}
}

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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',

View File

@ -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',
}

View File

@ -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');
}
@ -191,7 +191,7 @@ window.addEventListener('popstate', onHistoryChanged);
window.history.pushState = patchHistoryMethod('pushState');
window.history.replaceState = patchHistoryMethod('replaceState');
BxEventBus.Script.once('xcloudServerUnavailable', () => {
BxEventBus.Script.once('xcloud.server.unavailable', () => {
STATES.supportedRegion = false;
window.setTimeout(HeaderSection.watchHeader, 2000);

View File

@ -440,7 +440,9 @@ export class EmulatedMkbHandler extends MkbHandler {
}
if (this.enabled) {
document.body.requestPointerLock();
document.body.requestPointerLock({
unadjustedMovement: true,
});
} else {
document.pointerLockElement && document.exitPointerLock();
}

View File

@ -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",');
@ -842,9 +835,9 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
return str;
},
// 24225.js#4127, 24.17.11
patchSetCurrentlyFocusedInteractable(str: string) {
let index = str.indexOf('.setCurrentlyFocusedInteractable=(');
// 49851.js#4083, 27.0.4
patchSetCurrentFocus(str: string) {
let index = str.indexOf('.setCurrentFocus=(');
if (index < 0) {
return false;
}
@ -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',
@ -989,7 +1017,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
'supportLocalCoOp',
'overrideStorageGetSettings',
getPref(PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME) && 'patchSetCurrentlyFocusedInteractable',
getPref(PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME) && 'patchSetCurrentFocus',
getPref<UiLayout>(PrefKey.UI_LAYOUT) !== UiLayout.DEFAULT && 'websiteLayout',
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
@ -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

View File

@ -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) {

View File

@ -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);
}

View File

@ -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,

View File

@ -1,6 +1,6 @@
import { BxEvent } from "@/utils/bx-event";
import { BxIcon } from "@/utils/bx-icon";
import { CE, createSvgIcon, getReactProps, isElementVisible, secondsToHms } from "@/utils/html";
import { CE, createSvgIcon, getReactProps, isElementVisible, secondsToHm } from "@/utils/html";
import { XcloudApi } from "@/utils/xcloud-api";
interface GameTimeElement extends HTMLElement {
@ -30,8 +30,14 @@ export class GameTile {
if (typeof totalWaitTime === 'number' && isElementVisible($elm)) {
const $div = CE('div', { class: 'bx-game-tile-wait-time' },
createSvgIcon(BxIcon.PLAYTIME),
CE('span', {}, secondsToHms(totalWaitTime)),
CE('span', {}, totalWaitTime < 60 ? totalWaitTime + 's' : secondsToHm(totalWaitTime)),
);
const duration = (totalWaitTime >= 15 * 60) ? 'long' : (totalWaitTime >= 10 * 60) ? 'medium' : (totalWaitTime >= 5 * 60 ) ? 'short' : '';
if (duration) {
$div.dataset.duration = duration;
}
$elm.insertAdjacentElement('afterbegin', $div);
}
}

View File

@ -8,6 +8,9 @@ import { SettingsDialog } from "./dialog/settings-dialog";
import { TrueAchievements } from "@/utils/true-achievements";
import { BxIcon } from "@/utils/bx-icon";
import { BxEventBus } from "@/utils/bx-event-bus";
import { getPref } from "@/utils/settings-storages/global-settings-storage";
import { UiLayout } from "@/enums/pref-values";
import { PrefKey } from "@/enums/pref-keys";
export enum GuideMenuTab {
HOME = 'home',
@ -41,7 +44,7 @@ export class GuideMenu {
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.PRIMARY,
onClick: () => {
// Wait until the Guide dialog is closed
BxEventBus.Script.once('xcloudDialogDismissed', () => {
BxEventBus.Script.once('dialog.dismissed', () => {
setTimeout(() => SettingsDialog.getInstance().show(), 50);
});
@ -111,6 +114,11 @@ export class GuideMenu {
class: 'bx-guide-home-buttons',
});
// Set TV tag
if (STATES.userAgent.isTv || getPref<UiLayout>(PrefKey.UI_LAYOUT) === UiLayout.TV) {
document.body.dataset.bxMediaType = 'tv';
}
for (const $button of buttonsLayout) {
if (!$button) {
continue;

View File

@ -78,7 +78,7 @@ export class BxEventBus<TEvents extends Record<string, any>> {
BX_FLAGS.Debug && BxLogger.warning('EventBus', 'on', event, callback);
}
once<K extends keyof TEvents>(event: string, callback: EventCallback<TEvents[K]>): void {
once<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]>): void {
const wrapper = (...args: any[]) => {
// @ts-ignore
callback(...args);

View File

@ -54,7 +54,7 @@ export namespace BxEvent {
target.dispatchEvent(event);
AppInterface && AppInterface.onEvent(eventName);
BX_FLAGS.Debug && BxLogger.warning('BxEvent', 'dispatch', eventName, data);
BX_FLAGS.Debug && BxLogger.warning('BxEvent', 'dispatch', target, eventName, data);
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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]');
}

View File

@ -1,13 +1,14 @@
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,
EnableWifiWarnings: false,
EnableUpdateRequiredPage: false,
ShowForcedUpdateScreen: false,
EnableTakControlResizing: true, // Experimenting
};
// Enable Native Mouse & Keyboard
@ -17,12 +18,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;
}

View File

@ -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',

View File

@ -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,

View File

@ -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;

View File

@ -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",

View File

@ -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);

View File

@ -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;
}

View File

@ -82,6 +82,7 @@ export class XcloudInterceptor {
region.contintent = serverExtra[regionName][1];
} else {
region.contintent = 'other';
BX_FLAGS.Debug && alert('New server: ' + shortName);
}
}

View File

@ -114,6 +114,7 @@ export class BxNumberStepper extends HTMLInputElement implements BxHtmlSettingEl
self.$range = $range;
options.hideSlider && $range.classList.add('bx-gone');
$range.addEventListener('input', self.onRangeInput);
self.addEventListener('input', self.onRangeInput);
self.appendChild($range);