diff --git a/bun.lockb b/bun.lockb index aa5f64e..510294c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/dist/better-xcloud.lite.user.js b/dist/better-xcloud.lite.user.js index 06f07a9..43aaa3e 100644 --- a/dist/better-xcloud.lite.user.js +++ b/dist/better-xcloud.lite.user.js @@ -40,7 +40,7 @@ BxLogger.info("BxFlags", BX_FLAGS); var NATIVE_FETCH = window.fetch; var SMART_TV_UNIQUE_ID = "FC4A1DA2-711C-4E9C-BC7F-047AF8A672EA", CHROMIUM_VERSION = "123.0.0.0"; if (!!window.chrome || window.navigator.userAgent.includes("Chrome")) { - const match = window.navigator.userAgent.match(/\s(?:Chrome|Edg)\/([\d\.]+)/); + let match = window.navigator.userAgent.match(/\s(?:Chrome|Edg)\/([\d\.]+)/); if (match) CHROMIUM_VERSION = match[1]; } class UserAgent { @@ -62,7 +62,7 @@ class UserAgent { UserAgent.spoof(); } static updateStorage(profile, custom) { - const config = UserAgent.#config; + let config = UserAgent.#config; if (config.profile = profile, profile === "custom" && typeof custom !== "undefined") config.custom = custom; window.localStorage.setItem(UserAgent.STORAGE_KEY, JSON.stringify(config)); } @@ -70,7 +70,7 @@ class UserAgent { return window.navigator.orgUserAgent || window.navigator.userAgent; } static get(profile) { - const defaultUserAgent = window.navigator.userAgent; + let defaultUserAgent = window.navigator.userAgent; switch (profile) { case "default": return defaultUserAgent; @@ -82,22 +82,21 @@ class UserAgent { } static isSafari() { if (this.#isSafari !== null) return this.#isSafari; - const userAgent = UserAgent.getDefault().toLowerCase(); - let result = userAgent.includes("safari") && !userAgent.includes("chrom"); + let userAgent = UserAgent.getDefault().toLowerCase(), result = userAgent.includes("safari") && !userAgent.includes("chrom"); return this.#isSafari = result, result; } static isSafariMobile() { if (this.#isSafariMobile !== null) return this.#isSafariMobile; - const userAgent = UserAgent.getDefault().toLowerCase(), result = this.isSafari() && userAgent.includes("mobile"); + let userAgent = UserAgent.getDefault().toLowerCase(), result = this.isSafari() && userAgent.includes("mobile"); return this.#isSafariMobile = result, result; } static isMobile() { if (this.#isMobile !== null) return this.#isMobile; - const userAgent = UserAgent.getDefault().toLowerCase(), result = /iphone|ipad|android/.test(userAgent); + let userAgent = UserAgent.getDefault().toLowerCase(), result = /iphone|ipad|android/.test(userAgent); return this.#isMobile = result, result; } static spoof() { - const profile = UserAgent.#config.profile; + let profile = UserAgent.#config.profile; if (profile === "default") return; let newUserAgent = UserAgent.get(profile); if ("userAgentData" in window.navigator) window.navigator.orgUserAgentData = window.navigator.userAgentData, Object.defineProperty(window.navigator, "userAgentData", {}); @@ -106,11 +105,6 @@ class UserAgent { }); } } -function deepClone(obj) { - if ("structuredClone" in window) return structuredClone(obj); - if (!obj) return {}; - return JSON.parse(JSON.stringify(obj)); -} var SCRIPT_VERSION = "5.8.6-beta", 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, supportMkb = AppInterface || !userAgent.match(/(android|iphone|ipad)/), STATES = { @@ -138,6 +132,11 @@ var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.inclu remotePlay: {}, pointerServerPort: 9269 }, STORAGE = {}; +function deepClone(obj) { + if ("structuredClone" in window) return structuredClone(obj); + if (!obj) return {}; + return JSON.parse(JSON.stringify(obj)); +} var BxEvent; ((BxEvent) => { BxEvent.JUMP_BACK_IN_READY = "bx-jump-back-in-ready", BxEvent.POPSTATE = "bx-popstate", BxEvent.TITLE_INFO_READY = "bx-title-info-ready", BxEvent.SETTINGS_CHANGED = "bx-settings-changed", BxEvent.STREAM_LOADING = "bx-stream-loading", BxEvent.STREAM_STARTING = "bx-stream-starting", BxEvent.STREAM_STARTED = "bx-stream-started", BxEvent.STREAM_PLAYING = "bx-stream-playing", BxEvent.STREAM_STOPPED = "bx-stream-stopped", BxEvent.STREAM_ERROR_PAGE = "bx-stream-error-page", BxEvent.STREAM_WEBRTC_CONNECTED = "bx-stream-webrtc-connected", BxEvent.STREAM_WEBRTC_DISCONNECTED = "bx-stream-webrtc-disconnected", BxEvent.STREAM_SESSION_READY = "bx-stream-session-ready", BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED = "bx-custom-touch-layouts-loaded", BxEvent.TOUCH_LAYOUT_MANAGER_READY = "bx-touch-layout-manager-ready", BxEvent.REMOTE_PLAY_READY = "bx-remote-play-ready", BxEvent.REMOTE_PLAY_FAILED = "bx-remote-play-failed", BxEvent.XCLOUD_SERVERS_READY = "bx-servers-ready", BxEvent.XCLOUD_SERVERS_UNAVAILABLE = "bx-servers-unavailable", BxEvent.DATA_CHANNEL_CREATED = "bx-data-channel-created", BxEvent.GAME_BAR_ACTION_ACTIVATED = "bx-game-bar-action-activated", BxEvent.MICROPHONE_STATE_CHANGED = "bx-microphone-state-changed", BxEvent.SPEAKER_STATE_CHANGED = "bx-speaker-state-changed", BxEvent.CAPTURE_SCREENSHOT = "bx-capture-screenshot", BxEvent.POINTER_LOCK_REQUESTED = "bx-pointer-lock-requested", BxEvent.POINTER_LOCK_EXITED = "bx-pointer-lock-exited", BxEvent.NAVIGATION_FOCUS_CHANGED = "bx-nav-focus-changed", BxEvent.XCLOUD_DIALOG_SHOWN = "bx-xcloud-dialog-shown", BxEvent.XCLOUD_DIALOG_DISMISSED = "bx-xcloud-dialog-dismissed", BxEvent.XCLOUD_GUIDE_MENU_SHOWN = "bx-xcloud-guide-menu-shown", BxEvent.XCLOUD_POLLING_MODE_CHANGED = "bx-xcloud-polling-mode-changed", BxEvent.XCLOUD_RENDERING_COMPONENT = "bx-xcloud-rendering-component", BxEvent.XCLOUD_ROUTER_HISTORY_READY = "bx-xcloud-router-history-ready"; @@ -147,7 +146,7 @@ var BxEvent; alert("BxEvent.dispatch(): eventName is null"); return; } - const event = new Event(eventName); + let event = new Event(eventName); if (data) for (let key in data) event[key] = data[key]; target.dispatchEvent(event), AppInterface && AppInterface.onEvent(eventName), BX_FLAGS.Debug && BxLogger.warning("BxEvent", "dispatch", eventName, data); @@ -164,44 +163,6 @@ class NavigationUtils { } } var setNearby = NavigationUtils.setNearby; -function createElement(elmName, props = {}, ..._) { - let $elm; - const hasNs = "xmlns" in props; - if (hasNs) $elm = document.createElementNS(props.xmlns, elmName), delete props.xmlns; - else $elm = document.createElement(elmName); - if (props._nearby) setNearby($elm, props._nearby), delete props._nearby; - for (let key in props) { - if ($elm.hasOwnProperty(key)) continue; - if (hasNs) $elm.setAttributeNS(null, key, props[key]); - else if (key === "on") for (let eventName in props[key]) - $elm.addEventListener(eventName, props[key][eventName]); - else $elm.setAttribute(key, props[key]); - } - for (let i = 2, size = arguments.length;i < size; i++) { - const arg = arguments[i]; - if (arg instanceof Node) $elm.appendChild(arg); - else if (arg !== null && arg !== !1 && typeof arg !== "undefined") $elm.appendChild(document.createTextNode(arg)); - } - return $elm; -} -function isElementVisible($elm) { - const rect = $elm.getBoundingClientRect(); - return (rect.x >= 0 || rect.y >= 0) && !!rect.width && !!rect.height; -} -function removeChildElements($parent) { - while ($parent.firstElementChild) - $parent.firstElementChild.remove(); -} -function humanFileSize(size) { - const i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); - return (size / Math.pow(1024, i)).toFixed(2) + " " + FILE_SIZE_UNITS[i]; -} -function secondsToHm(seconds) { - let h = Math.floor(seconds / 3600), m = Math.floor(seconds % 3600 / 60) + 1; - if (m === 60) h += 1, m = 0; - const output = []; - return h > 0 && output.push(`${h}h`), m > 0 && output.push(`${m}m`), output.join(" "); -} var ButtonStyleClass = { 1: "bx-primary", 2: "bx-danger", @@ -215,22 +176,61 @@ var ButtonStyleClass = { 512: "bx-circular", 1024: "bx-normal-case", 2048: "bx-normal-link" -}, CE = createElement, svgParser = (svg) => new DOMParser().parseFromString(svg, "image/svg+xml").documentElement, createSvgIcon = (icon) => { +}; +function createElement(elmName, props = {}, ..._) { + let $elm, hasNs = "xmlns" in props; + if (hasNs) $elm = document.createElementNS(props.xmlns, elmName), delete props.xmlns; + else $elm = document.createElement(elmName); + if (props._nearby) setNearby($elm, props._nearby), delete props._nearby; + for (let key in props) { + if ($elm.hasOwnProperty(key)) continue; + if (hasNs) $elm.setAttributeNS(null, key, props[key]); + else if (key === "on") for (let eventName in props[key]) + $elm.addEventListener(eventName, props[key][eventName]); + else $elm.setAttribute(key, props[key]); + } + for (let i = 2, size = arguments.length;i < size; i++) { + let arg = arguments[i]; + if (arg instanceof Node) $elm.appendChild(arg); + else if (arg !== null && arg !== !1 && typeof arg !== "undefined") $elm.appendChild(document.createTextNode(arg)); + } + return $elm; +} +var CE = createElement, svgParser = (svg) => new DOMParser().parseFromString(svg, "image/svg+xml").documentElement, createSvgIcon = (icon) => { return svgParser(icon.toString()); }, ButtonStyleIndices = Object.keys(ButtonStyleClass).map((i) => parseInt(i)), createButton = (options) => { let $btn; if (options.url) $btn = CE("a", { class: "bx-button" }), $btn.href = options.url, $btn.target = "_blank"; else $btn = CE("button", { class: "bx-button", type: "button" }); - const style = options.style || 0; + let style = options.style || 0; style && ButtonStyleIndices.forEach((index) => { style & index && $btn.classList.add(ButtonStyleClass[index]); }), options.classes && $btn.classList.add(...options.classes), options.icon && $btn.appendChild(createSvgIcon(options.icon)), options.label && $btn.appendChild(CE("span", {}, options.label)), options.title && $btn.setAttribute("title", options.title), options.disabled && ($btn.disabled = !0), options.onClick && $btn.addEventListener("click", options.onClick), $btn.tabIndex = typeof options.tabIndex === "number" ? options.tabIndex : 0; for (let key in options.attributes) if (!$btn.hasOwnProperty(key)) $btn.setAttribute(key, options.attributes[key]); return $btn; -}, CTN = document.createTextNode.bind(document); +}; +function isElementVisible($elm) { + let rect = $elm.getBoundingClientRect(); + return (rect.x >= 0 || rect.y >= 0) && !!rect.width && !!rect.height; +} +var CTN = document.createTextNode.bind(document); window.BX_CE = createElement; +function removeChildElements($parent) { + while ($parent.firstElementChild) + $parent.firstElementChild.remove(); +} var FILE_SIZE_UNITS = ["B", "KB", "MB", "GB", "TB"]; +function humanFileSize(size) { + let i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); + return (size / Math.pow(1024, i)).toFixed(2) + " " + FILE_SIZE_UNITS[i]; +} +function secondsToHm(seconds) { + let h = Math.floor(seconds / 3600), m = Math.floor(seconds % 3600 / 60) + 1; + if (m === 60) h += 1, m = 0; + let output = []; + return h > 0 && output.push(`${h}h`), m > 0 && output.push(`${m}m`), output.join(" "); +} var SUPPORTED_LANGUAGES = { "en-US": "English (US)", "ca-CA": "Català", @@ -615,7 +615,7 @@ class Translations { let locale; if (newLocale) localStorage.setItem(Translations.#KEY_LOCALE, newLocale), locale = newLocale; else locale = localStorage.getItem(Translations.#KEY_LOCALE); - const supportedLocales = Translations.#supportedLocales; + let supportedLocales = Translations.#supportedLocales; if (!locale) { if (locale = window.navigator.language || Translations.#EN_US, supportedLocales.indexOf(locale) === -1) locale = Translations.#EN_US; localStorage.setItem(Translations.#KEY_LOCALE, locale); @@ -647,7 +647,7 @@ class Translations { } static async downloadTranslations(locale) { try { - const translations = await (await NATIVE_FETCH(`https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/translations/${locale}.json`)).json(); + let translations = await (await NATIVE_FETCH(`https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/translations/${locale}.json`)).json(); if (localStorage.getItem(Translations.#KEY_LOCALE) === locale) window.localStorage.setItem(Translations.#KEY_TRANSLATIONS, JSON.stringify(translations)), Translations.#foreignTranslations = translations; return !0; } catch (e) { @@ -681,63 +681,62 @@ var BypassServers = { }; class SettingElement { static #renderOptions(key, setting, currentValue, onChange) { - const $control = CE("select", { + let $control = CE("select", { tabindex: 0 - }); - let $parent; + }), $parent; if (setting.optionsGroup) $parent = CE("optgroup", { label: setting.optionsGroup }), $control.appendChild($parent); else $parent = $control; for (let value in setting.options) { - const label = setting.options[value], $option = CE("option", { value }, label); + let label = setting.options[value], $option = CE("option", { value }, label); $parent.appendChild($option); } return $control.value = currentValue, onChange && $control.addEventListener("input", (e) => { - const target = e.target, value = setting.type && setting.type === "number" ? parseInt(target.value) : target.value; + let target = e.target, value = setting.type && setting.type === "number" ? parseInt(target.value) : target.value; !e.ignoreOnChange && onChange(e, value); }), $control.setValue = (value) => { $control.value = value; }, $control; } static #renderMultipleOptions(key, setting, currentValue, onChange, params = {}) { - const $control = CE("select", { + let $control = CE("select", { multiple: !0, tabindex: 0 }); if (params && params.size) $control.setAttribute("size", params.size.toString()); for (let value in setting.multipleOptions) { - const label = setting.multipleOptions[value], $option = CE("option", { value }, label); + let label = setting.multipleOptions[value], $option = CE("option", { value }, label); $option.selected = currentValue.indexOf(value) > -1, $option.addEventListener("mousedown", function(e) { e.preventDefault(); - const target = e.target; + let target = e.target; target.selected = !target.selected; - const $parent = target.parentElement; + let $parent = target.parentElement; $parent.focus(), BxEvent.dispatch($parent, "input"); }), $control.appendChild($option); } return $control.addEventListener("mousedown", function(e) { - const self = this, orgScrollTop = self.scrollTop; + let self = this, orgScrollTop = self.scrollTop; window.setTimeout(() => self.scrollTop = orgScrollTop, 0); }), $control.addEventListener("mousemove", (e) => e.preventDefault()), onChange && $control.addEventListener("input", (e) => { - const target = e.target, values = Array.from(target.selectedOptions).map((i) => i.value); + let target = e.target, values = Array.from(target.selectedOptions).map((i) => i.value); !e.ignoreOnChange && onChange(e, values); }), $control; } static #renderNumber(key, setting, currentValue, onChange) { - const $control = CE("input", { + let $control = CE("input", { tabindex: 0, type: "number", min: setting.min, max: setting.max }); return $control.value = currentValue, onChange && $control.addEventListener("input", (e) => { - const target = e.target, value = Math.max(setting.min, Math.min(setting.max, parseInt(target.value))); + let target = e.target, value = Math.max(setting.min, Math.min(setting.max, parseInt(target.value))); target.value = value.toString(), !e.ignoreOnChange && onChange(e, value); }), $control; } static #renderCheckbox(key, setting, currentValue, onChange) { - const $control = CE("input", { type: "checkbox", tabindex: 0 }); + let $control = CE("input", { type: "checkbox", tabindex: 0 }); return $control.checked = currentValue, onChange && $control.addEventListener("input", (e) => { !e.ignoreOnChange && onChange(e, e.target.checked); }), $control.setValue = (value) => { @@ -746,8 +745,7 @@ class SettingElement { } static #renderNumberStepper(key, setting, value, onChange, options = {}) { options = options || {}, options.suffix = options.suffix || "", options.disabled = !!options.disabled, options.hideSlider = !!options.hideSlider; - let $text, $btnDec, $btnInc, $range = null, controlValue = value; - const { min: MIN, max: MAX } = setting, STEPS = Math.max(setting.steps || 1, 1), renderTextValue = (value2) => { + let $text, $btnDec, $btnInc, $range = null, controlValue = value, MIN = setting.min, MAX = setting.max, STEPS = Math.max(setting.steps || 1, 1), renderTextValue = (value2) => { value2 = parseInt(value2); let textContent = null; if (options.customTextValue) textContent = options.customTextValue(value2); @@ -781,7 +779,7 @@ class SettingElement { }), $wrapper.addEventListener("input", (e) => { BxEvent.dispatch($range, "input"); }), $wrapper.appendChild($range), options.ticks || options.exactTicks) { - const markersId = `markers-${key}`, $markers = CE("datalist", { id: markersId }); + let markersId = `markers-${key}`, $markers = CE("datalist", { id: markersId }); if ($range.setAttribute("list", markersId), options.exactTicks) { let start = Math.max(Math.floor(MIN / options.exactTicks), 1) * options.exactTicks; if (start === MIN) start += options.exactTicks; @@ -792,20 +790,18 @@ class SettingElement { $wrapper.appendChild($markers); } updateButtonsVisibility(); - let interval, isHolding = !1; - const onClick = (e) => { + let interval, isHolding = !1, onClick = (e) => { if (isHolding) { e.preventDefault(), isHolding = !1; return; } - const $btn = e.target; - let value2 = parseInt(controlValue); + let $btn = e.target, value2 = parseInt(controlValue); if ($btn.dataset.type === "dec") value2 = Math.max(MIN, value2 - STEPS); else value2 = Math.min(MAX, value2 + STEPS); controlValue = value2, updateButtonsVisibility(), $text.textContent = renderTextValue(value2), $range && ($range.value = value2.toString()), isHolding = !1, !e.ignoreOnChange && onChange && onChange(e, value2); }, onMouseDown = (e) => { e.preventDefault(), isHolding = !0; - const args = arguments; + let args = arguments; interval && clearInterval(interval), interval = window.setInterval(() => { e.target && BxEvent.dispatch(e.target, "click", { arguments: args @@ -828,14 +824,13 @@ class SettingElement { checkbox: SettingElement.#renderCheckbox }; static render(type, key, setting, currentValue, onChange, options) { - const method = SettingElement.#METHOD_MAP[type], $control = method(...Array.from(arguments).slice(1)); + let method = SettingElement.#METHOD_MAP[type], $control = method(...Array.from(arguments).slice(1)); if (type !== "number-stepper") $control.id = `bx_setting_${key}`; if (type === "options" || type === "multiple-options") $control.name = $control.id; return $control; } static fromPref(key, storage, onChange, overrideParams = {}) { - const definition = storage.getDefinition(key); - let currentValue = storage.getSetting(key), type; + let definition = storage.getDefinition(key), currentValue = storage.getSetting(key), type; if ("type" in definition) type = definition.type; else if ("options" in definition) type = "options"; else if ("multipleOptions" in definition) type = "multiple-options"; @@ -858,7 +853,7 @@ class BaseSettingsStore { this.storage = window.localStorage, this.storageKey = storageKey; let settingId; for (settingId in definitions) { - const setting = definitions[settingId]; + let setting = definitions[settingId]; if (typeof setting.requiredVariants === "string") setting.requiredVariants = [setting.requiredVariants]; setting.ready && setting.ready.call(this, setting); } @@ -866,12 +861,12 @@ class BaseSettingsStore { } get settings() { if (this._settings) return this._settings; - const settings = JSON.parse(this.storage.getItem(this.storageKey) || "{}"); + let settings = JSON.parse(this.storage.getItem(this.storageKey) || "{}"); return this._settings = settings, settings; } getDefinition(key) { if (!this.definitions[key]) { - const error = "Request invalid definition: " + key; + let error = "Request invalid definition: " + key; throw alert(error), Error(error); } return this.definitions[key]; @@ -881,7 +876,7 @@ class BaseSettingsStore { debugger; return; } - const definition = this.definitions[key]; + let definition = this.definitions[key]; if (definition.requiredVariants && !definition.requiredVariants.includes(SCRIPT_VARIANT)) return definition.default; if (checkUnsupported && definition.unsupported) return definition.default; if (!(key in this.settings)) this.settings[key] = this.validateValue(key, null); @@ -898,7 +893,7 @@ class BaseSettingsStore { this.storage.setItem(this.storageKey, JSON.stringify(this.settings)); } validateValue(key, value) { - const def = this.definitions[key]; + let def = this.definitions[key]; if (!def) return value; if (typeof value === "undefined" || value === null) value = def.default; if ("min" in def) value = Math.max(def.min, value); @@ -906,7 +901,7 @@ class BaseSettingsStore { if ("options" in def && !(value in def.options)) value = def.default; else if ("multipleOptions" in def) { if (value.length) { - const validOptions = Object.keys(def.multipleOptions); + let validOptions = Object.keys(def.multipleOptions); value.forEach((item, idx) => { validOptions.indexOf(item) === -1 && value.splice(idx, 1); }); @@ -919,16 +914,16 @@ class BaseSettingsStore { return this.definitions[key].label || key; } getValueText(key, value) { - const definition = this.definitions[key]; + let definition = this.definitions[key]; if (definition.type === "number-stepper") { - const params = definition.params; + let params = definition.params; if (params.customTextValue) { - const text = params.customTextValue(value); + let text = params.customTextValue(value); if (text) return text; } return value.toString(); } else if ("options" in definition) { - const options = definition.options; + let options = definition.options; if (value in options) return options[value]; } else if (typeof value === "boolean") return value ? t("on") : t("off"); return value.toString(); @@ -959,7 +954,7 @@ class StreamStatsCollector { fps: { current: 0, toString() { - const maxFps = getPref("video_max_fps"); + let maxFps = getPref("video_max_fps"); return maxFps < 60 ? `${maxFps}/${this.current}` : this.current.toString(); } }, @@ -973,7 +968,7 @@ class StreamStatsCollector { received: 0, dropped: 0, toString() { - const framesDroppedPercentage = (this.dropped * 100 / (this.dropped + this.received || 1)).toFixed(2); + let framesDroppedPercentage = (this.dropped * 100 / (this.dropped + this.received || 1)).toFixed(2); return framesDroppedPercentage === "0.00" ? this.dropped.toString() : `${this.dropped} (${framesDroppedPercentage}%)`; } }, @@ -981,7 +976,7 @@ class StreamStatsCollector { received: 0, dropped: 0, toString() { - const packetsLostPercentage = (this.dropped * 100 / (this.dropped + this.received || 1)).toFixed(2); + let packetsLostPercentage = (this.dropped * 100 / (this.dropped + this.received || 1)).toFixed(2); return packetsLostPercentage === "0.00" ? this.dropped.toString() : `${this.dropped} (${packetsLostPercentage}%)`; } }, @@ -1019,7 +1014,7 @@ class StreamStatsCollector { toString() { let text = `${this.current}%`; if (this.current !== this.start) { - const diffLevel = Math.round(this.current - this.start), sign = diffLevel > 0 ? "+" : ""; + let diffLevel = Math.round(this.current - this.start), sign = diffLevel > 0 ? "+" : ""; text += ` (${sign}${diffLevel}%)`; } return text; @@ -1037,51 +1032,51 @@ class StreamStatsCollector { }; lastVideoStat; async collect() { - const stats = await STATES.currentStream.peerConnection?.getStats(); + let stats = await STATES.currentStream.peerConnection?.getStats(); if (!stats) return; stats.forEach((stat) => { if (stat.type === "inbound-rtp" && stat.kind === "video") { - const fps = this.currentStats.fps; + let fps = this.currentStats.fps; fps.current = stat.framesPerSecond || 0; - const pl = this.currentStats.pl; + let pl = this.currentStats.pl; pl.dropped = Math.max(0, stat.packetsLost), pl.received = stat.packetsReceived; - const fl = this.currentStats.fl; + let fl = this.currentStats.fl; if (fl.dropped = stat.framesDropped, fl.received = stat.framesReceived, !this.lastVideoStat) { this.lastVideoStat = stat; return; } - const lastStat = this.lastVideoStat, jit = this.currentStats.jit, bufferDelayDiff = stat.jitterBufferDelay - lastStat.jitterBufferDelay, emittedCountDiff = stat.jitterBufferEmittedCount - lastStat.jitterBufferEmittedCount; + let lastStat = this.lastVideoStat, jit = this.currentStats.jit, bufferDelayDiff = stat.jitterBufferDelay - lastStat.jitterBufferDelay, emittedCountDiff = stat.jitterBufferEmittedCount - lastStat.jitterBufferEmittedCount; if (emittedCountDiff > 0) jit.current = bufferDelayDiff / emittedCountDiff * 1000; - const btr = this.currentStats.btr, timeDiff = stat.timestamp - lastStat.timestamp; + let btr = this.currentStats.btr, timeDiff = stat.timestamp - lastStat.timestamp; btr.current = 8 * (stat.bytesReceived - lastStat.bytesReceived) / timeDiff / 1000; - const dt = this.currentStats.dt; + let dt = this.currentStats.dt; dt.total = stat.totalDecodeTime - lastStat.totalDecodeTime; - const framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded; + let framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded; dt.current = dt.total / framesDecodedDiff * 1000, this.lastVideoStat = stat; } else if (stat.type === "candidate-pair" && stat.packetsReceived > 0 && stat.state === "succeeded") { - const ping = this.currentStats.ping; + let ping = this.currentStats.ping; ping.current = stat.currentRoundTripTime ? stat.currentRoundTripTime * 1000 : -1; - const dl = this.currentStats.dl; + let dl = this.currentStats.dl; dl.total = stat.bytesReceived; - const ul = this.currentStats.ul; + let ul = this.currentStats.ul; ul.total = stat.bytesSent; } }); let batteryLevel = 100, isCharging = !1; if (STATES.browser.capabilities.batteryApi) try { - const bm = await navigator.getBattery(); + let bm = await navigator.getBattery(); isCharging = bm.charging, batteryLevel = Math.round(bm.level * 100); } catch (e) {} - const battery = this.currentStats.batt; + let battery = this.currentStats.batt; battery.current = batteryLevel, battery.isCharging = isCharging; - const playTime = this.currentStats.play, now = +new Date; + let playTime = this.currentStats.play, now = +new Date; playTime.seconds = Math.ceil((now - playTime.startTime) / 1000); } getStat(kind) { return this.currentStats[kind]; } reset() { - const playTime = this.currentStats.play; + let playTime = this.currentStats.play; playTime.seconds = 0, playTime.startTime = +new Date; try { STATES.browser.capabilities.batteryApi && navigator.getBattery().then((bm) => { @@ -1096,15 +1091,14 @@ class StreamStatsCollector { } } function getSupportedCodecProfiles() { - const options = { + let options = { default: t("default") }; if (!("getCapabilities" in RTCRtpReceiver)) return options; - let hasLowCodec = !1, hasNormalCodec = !1, hasHighCodec = !1; - const codecs = RTCRtpReceiver.getCapabilities("video").codecs; + let hasLowCodec = !1, hasNormalCodec = !1, hasHighCodec = !1, codecs = RTCRtpReceiver.getCapabilities("video").codecs; for (let codec of codecs) { if (codec.mimeType.toLowerCase() !== "video/h264" || !codec.sdpFmtpLine) continue; - const fmtp = codec.sdpFmtpLine.toLowerCase(); + let fmtp = codec.sdpFmtpLine.toLowerCase(); if (fmtp.includes("profile-level-id=4d")) hasHighCodec = !0; else if (fmtp.includes("profile-level-id=42e")) hasNormalCodec = !0; else if (fmtp.includes("profile-level-id=420")) hasLowCodec = !0; @@ -1198,7 +1192,7 @@ class GlobalSettingsStorage extends BaseSettingsStore { default: "default", options: getSupportedCodecProfiles(), ready: (setting) => { - const options = setting.options, keys = Object.keys(options); + let options = setting.options, keys = Object.keys(options); if (keys.length <= 1) setting.unsupported = !0, setting.unsupportedNote = "⚠️ " + t("browser-unsupported-feature"); setting.suggest = { lowest: keys.length === 1 ? keys[0] : keys[1], @@ -1688,7 +1682,7 @@ class GlobalSettingsStorage extends BaseSettingsStore { size: 6 }, ready: (setting) => { - const multipleOptions = setting.multipleOptions; + let multipleOptions = setting.multipleOptions; if (!STATES.browser.capabilities.batteryApi) delete multipleOptions["batt"]; } }, @@ -1882,11 +1876,11 @@ class StreamStats { isGlancing = () => this.$container.dataset.display === "glancing"; quickGlanceSetup() { if (!STATES.isPlaying || this.quickGlanceObserver) return; - const $uiContainer = document.querySelector("div[data-testid=ui-container]"); + let $uiContainer = document.querySelector("div[data-testid=ui-container]"); if (!$uiContainer) return; this.quickGlanceObserver = new MutationObserver((mutationList, observer) => { for (let record of mutationList) { - const $target = record.target; + let $target = record.target; if (!$target.className || !$target.className.startsWith("GripHandle")) continue; if (record.target.ariaExpanded === "true") this.isHidden() && this.start(!0); else this.stop(!0); @@ -1905,20 +1899,18 @@ class StreamStats { this.onStoppedPlaying(); return; } - const PREF_STATS_CONDITIONAL_FORMATTING = getPref("stats_conditional_formatting"); - let grade = ""; - const statsCollector = StreamStatsCollector.getInstance(); + let PREF_STATS_CONDITIONAL_FORMATTING = getPref("stats_conditional_formatting"), grade = "", statsCollector = StreamStatsCollector.getInstance(); await statsCollector.collect(); let statKey; for (statKey in this.stats) { grade = ""; - const stat = this.stats[statKey], value = statsCollector.getStat(statKey), $element = stat.$element; + let stat = this.stats[statKey], value = statsCollector.getStat(statKey), $element = stat.$element; if ($element.textContent = value.toString(), PREF_STATS_CONDITIONAL_FORMATTING && "grades" in value) grade = statsCollector.calculateGrade(value.current, value.grades); if ($element.dataset.grade !== grade) $element.dataset.grade = grade; } } refreshStyles() { - const PREF_ITEMS = getPref("stats_items"), $container = this.$container; + let PREF_ITEMS = getPref("stats_items"), $container = this.$container; $container.dataset.stats = "[" + PREF_ITEMS.join("][") + "]", $container.dataset.position = getPref("stats_position"), $container.dataset.transparent = getPref("stats_transparent"), $container.style.opacity = getPref("stats_opacity") + "%", $container.style.fontSize = getPref("stats_text_size"); } hideSettingsUi() { @@ -1928,7 +1920,7 @@ class StreamStats { this.$container = CE("div", { class: "bx-stats-bar bx-gone" }); let statKey; for (statKey in this.stats) { - const stat = this.stats[statKey], $div = CE("div", { + let stat = this.stats[statKey], $div = CE("div", { class: `bx-stat-${statKey}`, title: stat.name }, CE("label", {}, statKey.toUpperCase()), stat.$element); @@ -1938,7 +1930,7 @@ class StreamStats { } static setupEvents() { window.addEventListener(BxEvent.STREAM_PLAYING, (e) => { - const PREF_STATS_QUICK_GLANCE = getPref("stats_quick_glance"), PREF_STATS_SHOW_WHEN_PLAYING = getPref("stats_show_when_playing"), streamStats = StreamStats.getInstance(); + let PREF_STATS_QUICK_GLANCE = getPref("stats_quick_glance"), PREF_STATS_SHOW_WHEN_PLAYING = getPref("stats_show_when_playing"), streamStats = StreamStats.getInstance(); if (PREF_STATS_SHOW_WHEN_PLAYING) streamStats.start(); else if (PREF_STATS_QUICK_GLANCE) streamStats.quickGlanceSetup(), !PREF_STATS_SHOW_WHEN_PLAYING && streamStats.start(!0); }); @@ -1957,7 +1949,7 @@ class Toast { static DURATION = 3000; static show(msg, status, options = {}) { options = options || {}; - const args = Array.from(arguments); + let args = Array.from(arguments); if (options.instant) Toast.stack = [args], Toast.showNext(); else Toast.stack.push(args), !Toast.isShowing && Toast.showNext(); } @@ -1967,22 +1959,22 @@ class Toast { return; } Toast.isShowing = !0, Toast.timeout && clearTimeout(Toast.timeout), Toast.timeout = window.setTimeout(Toast.hide, Toast.DURATION); - const [msg, status, options] = Toast.stack.shift(); + let [msg, status, options] = Toast.stack.shift(); if (options && options.html) Toast.$msg.innerHTML = msg; else Toast.$msg.textContent = msg; if (status) Toast.$status.classList.remove("bx-gone"), Toast.$status.textContent = status; else Toast.$status.classList.add("bx-gone"); - const classList = Toast.$wrapper.classList; + let classList = Toast.$wrapper.classList; classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-show"); } static hide() { Toast.timeout = null; - const classList = Toast.$wrapper.classList; + let classList = Toast.$wrapper.classList; classList.remove("bx-show"), classList.add("bx-hide"); } static setup() { Toast.$wrapper = CE("div", { class: "bx-toast bx-offscreen" }, Toast.$msg = CE("span", { class: "bx-toast-msg" }), Toast.$status = CE("span", { class: "bx-toast-status" })), Toast.$wrapper.addEventListener("transitionend", (e) => { - const classList = Toast.$wrapper.classList; + let classList = Toast.$wrapper.classList; if (classList.contains("bx-hide")) classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-offscreen"), Toast.showNext(); }), document.documentElement.appendChild(Toast.$wrapper); } @@ -2007,8 +1999,7 @@ function productTitleToSlug(title) { class SoundShortcut { static adjustGainNodeVolume(amount) { if (!getPref("audio_enable_volume_control")) return 0; - const currentValue = getPref("audio_volume"); - let nearestValue; + let currentValue = getPref("audio_volume"), nearestValue; if (amount > 0) nearestValue = ceilToNearest(currentValue, amount); else nearestValue = floorToNearest(currentValue, -1 * amount); let newValue; @@ -2021,8 +2012,7 @@ class SoundShortcut { } static muteUnmute() { if (getPref("audio_enable_volume_control") && STATES.currentStream.audioGainNode) { - const gainValue = STATES.currentStream.audioGainNode.gain.value, settingValue = getPref("audio_volume"); - let targetValue; + let gainValue = STATES.currentStream.audioGainNode.gain.value, settingValue = getPref("audio_volume"), targetValue; if (settingValue === 0) targetValue = 100, setPref("audio_volume", targetValue, !0); else if (gainValue === 0) targetValue = settingValue; else targetValue = 0; @@ -2038,7 +2028,7 @@ class SoundShortcut { if ($media = document.querySelector("div[data-testid=media-container] audio"), !$media) $media = document.querySelector("div[data-testid=media-container] video"); if ($media) { $media.muted = !$media.muted; - const status = $media.muted ? t("muted") : t("unmuted"); + let status = $media.muted ? t("muted") : t("unmuted"); Toast.show(`${t("stream")} ❯ ${t("volume")}`, status, { instant: !0 }), BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, { speakerState: $media.muted ? 1 : 0 }); @@ -2048,46 +2038,43 @@ class SoundShortcut { class BxSelectElement { static wrap($select) { $select.removeAttribute("tabindex"); - const $btnPrev = createButton({ + let $btnPrev = createButton({ label: "<", style: 32 }), $btnNext = createButton({ label: ">", style: 32 - }), isMultiple = $select.multiple; - let $checkBox, $label, visibleIndex = $select.selectedIndex, $content; + }), isMultiple = $select.multiple, $checkBox, $label, visibleIndex = $select.selectedIndex, $content; if (isMultiple) $content = CE("button", { class: "bx-select-value bx-focusable", tabindex: 0 }, $checkBox = CE("input", { type: "checkbox" }), $label = CE("span", {}, "")), $content.addEventListener("click", (e) => { $checkBox.click(); }), $checkBox.addEventListener("input", (e) => { - const $option = getOptionAtIndex(visibleIndex); + let $option = getOptionAtIndex(visibleIndex); $option && ($option.selected = e.target.checked), BxEvent.dispatch($select, "input"); }); else $content = CE("div", {}, $label = CE("label", { for: $select.id + "_checkbox" }, "")); - const getOptionAtIndex = (index) => { + let getOptionAtIndex = (index) => { return Array.from($select.querySelectorAll("option"))[index]; }, render = (e) => { if (e && e.manualTrigger) visibleIndex = $select.selectedIndex; visibleIndex = normalizeIndex(visibleIndex); - const $option = getOptionAtIndex(visibleIndex); - let content = ""; + let $option = getOptionAtIndex(visibleIndex), content = ""; if ($option) if (content = $option.textContent || "", content && $option.parentElement.tagName === "OPTGROUP") { $label.innerHTML = ""; - const fragment = document.createDocumentFragment(); + let fragment = document.createDocumentFragment(); fragment.appendChild(CE("span", {}, $option.parentElement.label)), fragment.appendChild(document.createTextNode(content)), $label.appendChild(fragment); } else $label.textContent = content; else $label.textContent = content; if ($label.classList.toggle("bx-line-through", $option && $option.disabled), isMultiple) $checkBox.checked = $option?.selected || !1, $checkBox.classList.toggle("bx-gone", !content); - const disablePrev = visibleIndex <= 0, disableNext = visibleIndex === $select.querySelectorAll("option").length - 1; + let disablePrev = visibleIndex <= 0, disableNext = visibleIndex === $select.querySelectorAll("option").length - 1; $btnPrev.classList.toggle("bx-inactive", disablePrev), $btnNext.classList.toggle("bx-inactive", disableNext), disablePrev && !disableNext && document.activeElement === $btnPrev && $btnNext.focus(), disableNext && !disablePrev && document.activeElement === $btnNext && $btnPrev.focus(); }, normalizeIndex = (index) => { return Math.min(Math.max(index, 0), $select.querySelectorAll("option").length - 1); }, onPrevNext = (e) => { if (!e.target) return; - const goNext = e.target.closest("button") === $btnNext, currentIndex = visibleIndex; - let newIndex = goNext ? currentIndex + 1 : currentIndex - 1; + let goNext = e.target.closest("button") === $btnNext, currentIndex = visibleIndex, newIndex = goNext ? currentIndex + 1 : currentIndex - 1; if (newIndex = normalizeIndex(newIndex), visibleIndex = newIndex, !isMultiple && newIndex !== currentIndex) $select.selectedIndex = newIndex; if (isMultiple) render(); else BxEvent.dispatch($select, "input"); @@ -2101,7 +2088,7 @@ class BxSelectElement { childList: !0, attributes: !0 }), render(); - const $div = CE("div", { + let $div = CE("div", { class: "bx-select", _nearby: { orientation: "horizontal", @@ -2128,10 +2115,9 @@ class BxSelectElement { } } function onChangeVideoPlayerType() { - const playerType = getPref("video_player_type"), $videoProcessing = document.getElementById(`bx_setting_${"video_processing"}`), $videoSharpness = document.getElementById(`bx_setting_${"video_sharpness"}`), $videoPowerPreference = document.getElementById(`bx_setting_${"video_power_preference"}`), $videoMaxFps = document.getElementById(`bx_setting_${"video_max_fps"}`); + let playerType = getPref("video_player_type"), $videoProcessing = document.getElementById(`bx_setting_${"video_processing"}`), $videoSharpness = document.getElementById(`bx_setting_${"video_sharpness"}`), $videoPowerPreference = document.getElementById(`bx_setting_${"video_power_preference"}`), $videoMaxFps = document.getElementById(`bx_setting_${"video_max_fps"}`); if (!$videoProcessing) return; - let isDisabled = !1; - const $optCas = $videoProcessing.querySelector(`option[value=${"cas"}]`); + let isDisabled = !1, $optCas = $videoProcessing.querySelector(`option[value=${"cas"}]`); if (playerType === "webgl2") $optCas && ($optCas.disabled = !1); else if ($videoProcessing.value = "usm", setPref("video_processing", "usm"), $optCas && ($optCas.disabled = !0), UserAgent.isSafari()) isDisabled = !0; $videoProcessing.disabled = isDisabled, $videoSharpness.dataset.disabled = isDisabled.toString(), $videoPowerPreference.closest(".bx-settings-row").classList.toggle("bx-gone", playerType !== "webgl2"), $videoMaxFps.closest(".bx-settings-row").classList.toggle("bx-gone", playerType !== "webgl2"), updateVideoPlayer(); @@ -2140,10 +2126,10 @@ function limitVideoPlayerFps(targetFps) { STATES.currentStream.streamPlayer?.getWebGL2Player()?.setTargetFps(targetFps); } function updateVideoPlayer() { - const streamPlayer = STATES.currentStream.streamPlayer; + let streamPlayer = STATES.currentStream.streamPlayer; if (!streamPlayer) return; limitVideoPlayerFps(getPref("video_max_fps")); - const options = { + let options = { processing: getPref("video_processing"), sharpness: getPref("video_sharpness"), saturation: getPref("video_saturation"), @@ -2235,16 +2221,16 @@ class MkbPreset { } }; static convert(preset) { - const obj = { + let obj = { mapping: {}, mouse: Object.assign({}, preset.mouse) }; for (let buttonIndex in preset.mapping) for (let keyName of preset.mapping[parseInt(buttonIndex)]) obj.mapping[keyName] = parseInt(buttonIndex); - const mouse = obj.mouse; + let mouse = obj.mouse; mouse["sensitivity_x"] *= EmulatedMkbHandler.DEFAULT_PANNING_SENSITIVITY, mouse["sensitivity_y"] *= EmulatedMkbHandler.DEFAULT_PANNING_SENSITIVITY, mouse["deadzone_counterweight"] *= EmulatedMkbHandler.DEFAULT_DEADZONE_COUNTERWEIGHT; - const mouseMapTo = MouseMapTo[mouse["map_to"]]; + let mouseMapTo = MouseMapTo[mouse["map_to"]]; if (typeof mouseMapTo !== "undefined") mouse["map_to"] = mouseMapTo; else mouse["map_to"] = MkbPreset.MOUSE_SETTINGS["map_to"].default; return console.log(obj), obj; @@ -2266,9 +2252,9 @@ class LocalDb { resolve(); return; } - const request = window.indexedDB.open(LocalDb.DB_NAME, LocalDb.DB_VERSION); + let request = window.indexedDB.open(LocalDb.DB_NAME, LocalDb.DB_VERSION); request.onupgradeneeded = (e) => { - const db = e.target.result; + let db = e.target.result; switch (e.oldVersion) { case 0: { db.createObjectStore(LocalDb.TABLE_PRESETS, { keyPath: "id", autoIncrement: !0 }).createIndex("name_idx", "name"); @@ -2283,13 +2269,13 @@ class LocalDb { }); } #table(name, type) { - const table = this.#DB.transaction(name, type || "readonly").objectStore(name); + let table = this.#DB.transaction(name, type || "readonly").objectStore(name); return new Promise((resolve) => resolve(table)); } #call(method) { - const table = arguments[1]; + let table = arguments[1]; return new Promise((resolve) => { - const request = method.call(table, ...Array.from(arguments).slice(2)); + let request = method.call(table, ...Array.from(arguments).slice(2)); request.onsuccess = (e) => { resolve([table, e.target.result]); }; @@ -2329,11 +2315,11 @@ class LocalDb { return this.#open().then(() => this.#table(LocalDb.TABLE_PRESETS, "readwrite")).then((table) => this.#count(table)).then(([table, count]) => { if (count > 0) return new Promise((resolve) => { this.#getAll(table).then(([table2, items]) => { - const presets = {}; + let presets = {}; items.forEach((item) => presets[item.id] = item), resolve(presets); }); }); - const preset = { + let preset = { name: t("default"), data: MkbPreset.DEFAULT_PRESET }; @@ -2387,8 +2373,7 @@ class PointerClient { }), this.socket.addEventListener("close", (event) => { this.socket = null; }), this.socket.addEventListener("message", (event) => { - const dataView = new DataView(event.data); - let messageType = dataView.getInt8(0), offset = Int8Array.BYTES_PER_ELEMENT; + let dataView = new DataView(event.data), messageType = dataView.getInt8(0), offset = Int8Array.BYTES_PER_ELEMENT; switch (messageType) { case 1: this.onMove(dataView, offset); @@ -2406,25 +2391,25 @@ class PointerClient { }); } onMove(dataView, offset) { - const x = dataView.getInt16(offset); + let x = dataView.getInt16(offset); offset += Int16Array.BYTES_PER_ELEMENT; - const y = dataView.getInt16(offset); + let y = dataView.getInt16(offset); this.mkbHandler?.handleMouseMove({ movementX: x, movementY: y }); } onPress(messageType, dataView, offset) { - const button = dataView.getUint8(offset); + let button = dataView.getUint8(offset); this.mkbHandler?.handleMouseClick({ pointerButton: button, pressed: messageType === 2 }); } onScroll(dataView, offset) { - const vScroll = dataView.getInt16(offset); + let vScroll = dataView.getInt16(offset); offset += Int16Array.BYTES_PER_ELEMENT; - const hScroll = dataView.getInt16(offset); + let hScroll = dataView.getInt16(offset); this.mkbHandler?.handleMouseWheel({ vertical: vScroll, horizontal: hScroll @@ -2490,7 +2475,7 @@ class PointerLockMouseDataProvider extends MouseDataProvider { }; #onMouseEvent = (e) => { e.preventDefault(); - const isMouseDown = e.type === "mousedown", data = { + let isMouseDown = e.type === "mousedown", data = { mouseButton: e.button, pressed: isMouseDown }; @@ -2498,7 +2483,7 @@ class PointerLockMouseDataProvider extends MouseDataProvider { }; #onWheelEvent = (e) => { if (!KeyHelper.getKeyFromEvent(e)) return; - const data = { + let data = { vertical: e.deltaY, horizontal: e.deltaX }; @@ -2553,24 +2538,24 @@ class EmulatedMkbHandler extends MkbHandler { } isEnabled = () => this.#enabled; #patchedGetGamepads = () => { - const gamepads = this.#nativeGetGamepads() || []; + let gamepads = this.#nativeGetGamepads() || []; return gamepads[this.#VIRTUAL_GAMEPAD.index] = this.#VIRTUAL_GAMEPAD, gamepads; }; #getVirtualGamepad = () => this.#VIRTUAL_GAMEPAD; #updateStick(stick, x, y) { - const virtualGamepad = this.#getVirtualGamepad(); + let virtualGamepad = this.#getVirtualGamepad(); virtualGamepad.axes[stick * 2] = x, virtualGamepad.axes[stick * 2 + 1] = y, virtualGamepad.timestamp = performance.now(); } #vectorLength = (x, y) => Math.sqrt(x ** 2 + y ** 2); #resetGamepad = () => { - const gamepad = this.#getVirtualGamepad(); + let gamepad = this.#getVirtualGamepad(); gamepad.axes = [0, 0, 0, 0]; for (let button of gamepad.buttons) button.pressed = !1, button.value = 0; gamepad.timestamp = performance.now(); }; #pressButton = (buttonIndex, pressed) => { - const virtualGamepad = this.#getVirtualGamepad(); + let virtualGamepad = this.#getVirtualGamepad(); if (buttonIndex >= 100) { let [valueArr, axisIndex] = this.#STICK_MAP[buttonIndex]; valueArr = valueArr, axisIndex = axisIndex; @@ -2585,7 +2570,7 @@ class EmulatedMkbHandler extends MkbHandler { virtualGamepad.timestamp = performance.now(); }; #onKeyboardEvent = (e) => { - const isKeyDown = e.type === "keydown"; + let isKeyDown = e.type === "keydown"; if (e.code === "F8") { if (!isKeyDown) e.preventDefault(), this.toggle(); return; @@ -2598,38 +2583,37 @@ class EmulatedMkbHandler extends MkbHandler { return; } if (!this.#isPolling) return; - const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[e.code || e.key]; + let buttonIndex = this.#CURRENT_PRESET_DATA.mapping[e.code || e.key]; if (typeof buttonIndex === "undefined") return; if (e.repeat) return; e.preventDefault(), this.#pressButton(buttonIndex, isKeyDown); }; #onMouseStopped = () => { this.#detectMouseStoppedTimeout = null; - const analog = this.#CURRENT_PRESET_DATA.mouse["map_to"] === 1 ? 0 : 1; + let analog = this.#CURRENT_PRESET_DATA.mouse["map_to"] === 1 ? 0 : 1; this.#updateStick(analog, 0, 0); }; handleMouseClick = (data) => { let mouseButton; if (typeof data.mouseButton !== "undefined") mouseButton = data.mouseButton; else if (typeof data.pointerButton !== "undefined") mouseButton = PointerToMouseButton[data.pointerButton]; - const keyCode = "Mouse" + mouseButton, key = { + let keyCode = "Mouse" + mouseButton, key = { code: keyCode, name: KeyHelper.codeToKeyName(keyCode) }; if (!key.name) return; - const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code]; + let buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code]; if (typeof buttonIndex === "undefined") return; this.#pressButton(buttonIndex, data.pressed); }; handleMouseMove = (data) => { - const mouseMapTo = this.#CURRENT_PRESET_DATA.mouse["map_to"]; + let mouseMapTo = this.#CURRENT_PRESET_DATA.mouse["map_to"]; if (mouseMapTo === 0) return; this.#detectMouseStoppedTimeout && clearTimeout(this.#detectMouseStoppedTimeout), this.#detectMouseStoppedTimeout = window.setTimeout(this.#onMouseStopped.bind(this), 50); - const deadzoneCounterweight = this.#CURRENT_PRESET_DATA.mouse["deadzone_counterweight"]; - let x = data.movementX * this.#CURRENT_PRESET_DATA.mouse["sensitivity_x"], y = data.movementY * this.#CURRENT_PRESET_DATA.mouse["sensitivity_y"], length = this.#vectorLength(x, y); + let deadzoneCounterweight = this.#CURRENT_PRESET_DATA.mouse["deadzone_counterweight"], x = data.movementX * this.#CURRENT_PRESET_DATA.mouse["sensitivity_x"], y = data.movementY * this.#CURRENT_PRESET_DATA.mouse["sensitivity_y"], length = this.#vectorLength(x, y); if (length !== 0 && length < deadzoneCounterweight) x *= deadzoneCounterweight / length, y *= deadzoneCounterweight / length; else if (length > EmulatedMkbHandler.MAXIMUM_STICK_RANGE) x *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length, y *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length; - const analog = mouseMapTo === 1 ? 0 : 1; + let analog = mouseMapTo === 1 ? 0 : 1; this.#updateStick(analog, x, y); }; handleMouseWheel = (data) => { @@ -2639,7 +2623,7 @@ class EmulatedMkbHandler extends MkbHandler { else if (data.horizontal < 0) code = "ScrollLeft"; else if (data.horizontal > 0) code = "ScrollRight"; if (!code) return !1; - const key = { + let key = { code, name: KeyHelper.codeToKeyName(code) }, buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code]; @@ -2657,7 +2641,7 @@ class EmulatedMkbHandler extends MkbHandler { }; #getCurrentPreset = () => { return new Promise((resolve) => { - const presetId = getPref("mkb_default_preset_id"); + let presetId = getPref("mkb_default_preset_id"); LocalDb.INSTANCE.getPreset(presetId).then((preset) => { resolve(preset); }); @@ -2696,7 +2680,7 @@ class EmulatedMkbHandler extends MkbHandler { label: t("edit"), onClick: (e) => { e.preventDefault(), e.stopPropagation(); - const dialog = SettingsNavigationDialog.getInstance(); + let dialog = SettingsNavigationDialog.getInstance(); dialog.focusTab("mkb"), NavigationDialogManager.getInstance().show(dialog); } })))); @@ -2741,14 +2725,14 @@ class EmulatedMkbHandler extends MkbHandler { start = () => { if (!this.#enabled) this.#enabled = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 }); this.#isPolling = !0, this.#escKeyDownTime = -1, this.#resetGamepad(), window.navigator.getGamepads = this.#patchedGetGamepads, this.waitForMouseData(!1), this.#mouseDataProvider?.start(); - const virtualGamepad = this.#getVirtualGamepad(); + let virtualGamepad = this.#getVirtualGamepad(); virtualGamepad.connected = !0, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepadconnected", { gamepad: virtualGamepad }), window.BX_EXPOSED.stopTakRendering = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 }); }; stop = () => { this.#enabled = !1, this.#isPolling = !1, this.#escKeyDownTime = -1; - const virtualGamepad = this.#getVirtualGamepad(); + let virtualGamepad = this.#getVirtualGamepad(); if (virtualGamepad.connected) this.#resetGamepad(), virtualGamepad.connected = !1, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepaddisconnected", { gamepad: virtualGamepad }), window.navigator.getGamepads = this.#nativeGetGamepads; @@ -2768,7 +2752,7 @@ class NavigationDialog { NavigationDialogManager.getInstance().hide(); } getFocusedElement() { - const $activeElement = document.activeElement; + let $activeElement = document.activeElement; if (!$activeElement) return null; if (this.$container.contains($activeElement)) return $activeElement; return null; @@ -2832,20 +2816,19 @@ class NavigationDialogManager { }), document.documentElement.appendChild(this.$overlay), this.$container = CE("div", { class: "bx-navigation-dialog bx-gone" }), document.documentElement.appendChild(this.$container), window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, (e) => this.hide()), getPref("ui_controller_friendly")) new MutationObserver((mutationList) => { if (mutationList.length === 0 || mutationList[0].addedNodes.length === 0) return; - const $dialog = mutationList[0].addedNodes[0]; + let $dialog = mutationList[0].addedNodes[0]; if (!$dialog || !($dialog instanceof HTMLElement)) return; this.calculateSelectBoxes($dialog); }).observe(this.$container, { childList: !0 }); } calculateSelectBoxes($root) { $root.querySelectorAll(".bx-select:not([data-calculated]) select").forEach(($select) => { - const $parent = $select.parentElement; + let $parent = $select.parentElement; if ($parent.classList.contains("bx-full-width")) { $parent.dataset.calculated = "true"; return; } - const rect = $select.getBoundingClientRect(); - let $label, width = Math.ceil(rect.width); + let rect = $select.getBoundingClientRect(), $label, width = Math.ceil(rect.width); if (!width) return; if ($select.multiple) $label = $parent.querySelector(".bx-select-value"), width += 20; else $label = $parent.querySelector("div"); @@ -2855,8 +2838,7 @@ class NavigationDialogManager { handleEvent(event) { switch (event.type) { case "keydown": - const $target = event.target, keyboardEvent = event, keyCode = keyboardEvent.code || keyboardEvent.key; - let handled = this.dialog?.handleKeyPress(keyCode); + let $target = event.target, keyboardEvent = event, keyCode = keyboardEvent.code || keyboardEvent.key, handled = this.dialog?.handleKeyPress(keyCode); if (handled) { event.preventDefault(), event.stopPropagation(); return; @@ -2875,12 +2857,11 @@ class NavigationDialogManager { return this.$container && !this.$container.classList.contains("bx-gone"); } pollGamepad() { - const gamepads = window.navigator.getGamepads(); + let gamepads = window.navigator.getGamepads(); for (let gamepad of gamepads) { if (!gamepad || !gamepad.connected) continue; if (gamepad.id === VIRTUAL_GAMEPAD_ID) continue; - const { axes, buttons } = gamepad; - let releasedButton = null, heldButton = null, lastState = this.gamepadLastStates[gamepad.index], lastTimestamp, lastKey, lastKeyPressed; + let { axes, buttons } = gamepad, releasedButton = null, heldButton = null, lastState = this.gamepadLastStates[gamepad.index], lastTimestamp, lastKey, lastKeyPressed; if (lastState) [lastTimestamp, lastKey, lastKeyPressed] = lastState; if (lastTimestamp && lastTimestamp === gamepad.timestamp) continue; for (let key of NavigationDialogManager.GAMEPAD_KEYS) @@ -2893,7 +2874,7 @@ class NavigationDialogManager { } if (heldButton === null && releasedButton === null && axes && axes.length >= 2) { if (lastKey) { - const releasedHorizontal = Math.abs(axes[0]) < 0.1 && (lastKey === 102 || lastKey === 103), releasedVertical = Math.abs(axes[1]) < 0.1 && (lastKey === 100 || lastKey === 101); + let releasedHorizontal = Math.abs(axes[0]) < 0.1 && (lastKey === 102 || lastKey === 103), releasedVertical = Math.abs(axes[1]) < 0.1 && (lastKey === 100 || lastKey === 101); if (releasedHorizontal || releasedVertical) releasedButton = lastKey; else heldButton = lastKey; } else if (axes[0] < -0.5) heldButton = 102; @@ -2903,7 +2884,7 @@ class NavigationDialogManager { } if (heldButton !== null) { if (this.gamepadLastStates[gamepad.index] = [gamepad.timestamp, heldButton, !1], this.clearGamepadHoldingInterval(), NavigationDialogManager.GAMEPAD_DIRECTION_MAP[heldButton]) this.gamepadHoldingIntervalId = window.setInterval(() => { - const lastState2 = this.gamepadLastStates[gamepad.index]; + let lastState2 = this.gamepadLastStates[gamepad.index]; if (lastState2) { if ([lastTimestamp, lastKey, lastKeyPressed] = lastState2, lastKey === heldButton) { this.handleGamepad(gamepad, heldButton); @@ -2935,7 +2916,7 @@ class NavigationDialogManager { let direction = NavigationDialogManager.GAMEPAD_DIRECTION_MAP[key]; if (!direction) return !1; if (document.activeElement instanceof HTMLInputElement && document.activeElement.type === "range") { - const $range = document.activeElement; + let $range = document.activeElement; if (direction === 4 || direction === 2) $range.value = (parseInt($range.value) + parseInt($range.step) * (direction === 4 ? -1 : 1)).toString(), $range.dispatchEvent(new InputEvent("input")), handled = !0; } if (!handled) this.focusDirection(direction); @@ -2958,11 +2939,11 @@ class NavigationDialogManager { return $elm.focus(), $elm === document.activeElement; } getOrientation($elm) { - const nearby = $elm.nearby || {}; + let nearby = $elm.nearby || {}; if (nearby.selfOrientation) return nearby.selfOrientation; let orientation, $current = $elm.parentElement; while ($current !== this.$container) { - const tmp = $current.nearby?.orientation; + let tmp = $current.nearby?.orientation; if ($current.nearby && tmp) { orientation = tmp; break; @@ -2977,14 +2958,12 @@ class NavigationDialogManager { if (!$focusing || $focusing === this.$container) return null; if (checked.includes($focusing)) return null; checked.push($focusing); - let $target = $focusing; - const $parent = $target.parentElement, nearby = $target.nearby || {}, orientation = this.getOrientation($target); - let siblingProperty = NavigationDialogManager.SIBLING_PROPERTY_MAP[orientation][direction]; + let $target = $focusing, $parent = $target.parentElement, nearby = $target.nearby || {}, orientation = this.getOrientation($target), siblingProperty = NavigationDialogManager.SIBLING_PROPERTY_MAP[orientation][direction]; if (siblingProperty) { let $sibling = $target; while ($sibling[siblingProperty]) { $sibling = $sibling[siblingProperty]; - const $focusable = this.findFocusableElement($sibling, direction); + let $focusable = this.findFocusableElement($sibling, direction); if ($focusable) return $focusable; } } @@ -2999,18 +2978,18 @@ class NavigationDialogManager { if (!!$elm.disabled) return null; if (!isElementVisible($elm)) return null; if ($elm.tabIndex > -1) return $elm; - const focus = $elm.nearby?.focus; + let focus = $elm.nearby?.focus; if (focus) { if (focus instanceof HTMLElement) return this.findFocusableElement(focus, direction); else if (typeof focus === "function") { if (focus()) return document.activeElement; } } - const children = Array.from($elm.children), orientation = $elm.nearby?.orientation || "vertical"; + let children = Array.from($elm.children), orientation = $elm.nearby?.orientation || "vertical"; if (orientation === "horizontal" || orientation === "vertical" && direction === 1) children.reverse(); for (let $child of children) { if (!$child || !($child instanceof HTMLElement)) return null; - const $target = this.findFocusableElement($child, direction); + let $target = this.findFocusableElement($child, direction); if ($target) return $target; } return null; @@ -3022,15 +3001,15 @@ class NavigationDialogManager { this.gamepadLastStates = [], this.gamepadPollingIntervalId && window.clearInterval(this.gamepadPollingIntervalId), this.gamepadPollingIntervalId = null; } focusDirection(direction) { - const dialog = this.dialog; + let dialog = this.dialog; if (!dialog) return; - const $focusing = dialog.getFocusedElement(); + let $focusing = dialog.getFocusedElement(); if (!$focusing || !this.findFocusableElement($focusing, direction)) return dialog.focusIfNeeded(), null; - const $target = this.findNextTarget($focusing, direction, !0); + let $target = this.findNextTarget($focusing, direction, !0); this.focus($target); } unmountCurrentDialog() { - const dialog = this.dialog; + let dialog = this.dialog; dialog && dialog.onBeforeUnmount(), this.$container.firstChild?.remove(), dialog && dialog.onUnmounted(), this.dialog = null; } } @@ -3086,12 +3065,12 @@ class VibrationManager { AppInterface.vibrate(JSON.stringify(data), window.BX_VIBRATION_INTENSITY); return; } - const intensity = Math.min(100, data.leftMotorPercent + data.rightMotorPercent / 2) * window.BX_VIBRATION_INTENSITY; + let intensity = Math.min(100, data.leftMotorPercent + data.rightMotorPercent / 2) * window.BX_VIBRATION_INTENSITY; if (intensity === 0 || intensity === 100) { window.navigator.vibrate(intensity ? data.durationMs : 0); return; } - const pulseDuration = 200, onDuration = Math.floor(pulseDuration * intensity / 100), offDuration = pulseDuration - onDuration, repeats = Math.ceil(data.durationMs / pulseDuration), pulses = Array(repeats).fill([onDuration, offDuration]).flat(); + let pulseDuration = 200, onDuration = Math.floor(pulseDuration * intensity / 100), offDuration = pulseDuration - onDuration, repeats = Math.ceil(data.durationMs / pulseDuration), pulses = Array(repeats).fill([onDuration, offDuration]).flat(); window.navigator.vibrate(pulses); } static supportControllerVibration() { @@ -3106,12 +3085,11 @@ class VibrationManager { return; } stopVibration && window.navigator.vibrate(0); - const value = getPref("controller_device_vibration"); - let enabled; + let value = getPref("controller_device_vibration"), enabled; if (value === "on") enabled = !0; else if (value === "auto") { enabled = !0; - const gamepads = window.navigator.getGamepads(); + let gamepads = window.navigator.getGamepads(); for (let gamepad of gamepads) if (gamepad) { enabled = !1; @@ -3123,15 +3101,13 @@ class VibrationManager { static #onMessage(e) { if (!window.BX_ENABLE_DEVICE_VIBRATION) return; if (typeof e !== "object" || !(e.data instanceof ArrayBuffer)) return; - const dataView = new DataView(e.data); - let offset = 0, messageType; + let dataView = new DataView(e.data), offset = 0, messageType; if (dataView.byteLength === 13) messageType = dataView.getUint16(offset, !0), offset += Uint16Array.BYTES_PER_ELEMENT; else messageType = dataView.getUint8(offset), offset += Uint8Array.BYTES_PER_ELEMENT; if (!(messageType & 128)) return; - const vibrationType = dataView.getUint8(offset); + let vibrationType = dataView.getUint8(offset); if (offset += Uint8Array.BYTES_PER_ELEMENT, vibrationType !== 0) return; - const data = {}; - let key; + let data = {}, key; for (key in VIBRATION_DATA_MAP) if (VIBRATION_DATA_MAP[key] === 16) data[key] = dataView.getUint16(offset, !0), offset += Uint16Array.BYTES_PER_ELEMENT; else data[key] = dataView.getUint8(offset), offset += Uint8Array.BYTES_PER_ELEMENT; @@ -3139,7 +3115,7 @@ class VibrationManager { } static initialSetup() { window.addEventListener("gamepadconnected", (e) => VibrationManager.updateGlobalVars()), window.addEventListener("gamepaddisconnected", (e) => VibrationManager.updateGlobalVars()), VibrationManager.updateGlobalVars(!1), window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => { - const dataChannel = e.dataChannel; + let dataChannel = e.dataChannel; if (!dataChannel || dataChannel.label !== "input") return; dataChannel.addEventListener("message", VibrationManager.#onMessage); }); @@ -3195,9 +3171,9 @@ class SettingsNavigationDialog extends NavigationDialog { helpUrl: "https://better-xcloud.github.io/features/", items: [ ($parent) => { - const PREF_LATEST_VERSION = getPref("version_latest"), topButtons = []; + let PREF_LATEST_VERSION = getPref("version_latest"), topButtons = []; if (!SCRIPT_VERSION.includes("beta") && PREF_LATEST_VERSION && PREF_LATEST_VERSION != SCRIPT_VERSION) { - const opts = { + let opts = { label: "🌟 " + t("new-version-available", { version: PREF_LATEST_VERSION }), style: 1 | 32 | 64 }; @@ -3231,7 +3207,7 @@ class SettingsNavigationDialog extends NavigationDialog { class: "bx-suggest-toggler bx-focusable", tabindex: 0 }, CE("label", {}, t("suggest-settings")), CE("span", {}, "❯")), this.$btnSuggestion.addEventListener("click", this.renderSuggestions.bind(this)), topButtons.push(this.$btnSuggestion); - const $div = CE("div", { + let $div = CE("div", { class: "bx-top-buttons", _nearby: { orientation: "vertical" @@ -3345,7 +3321,7 @@ class SettingsNavigationDialog extends NavigationDialog { { pref: "user_agent_profile", onCreated: (setting, $control) => { - const defaultUserAgent = window.navigator.orgUserAgent || window.navigator.userAgent, $inpCustomUserAgent = CE("input", { + let defaultUserAgent = window.navigator.orgUserAgent || window.navigator.userAgent, $inpCustomUserAgent = CE("input", { id: `bx_setting_inp_${setting.pref}`, type: "text", placeholder: defaultUserAgent, @@ -3354,7 +3330,7 @@ class SettingsNavigationDialog extends NavigationDialog { tabindex: 0 }); $inpCustomUserAgent.addEventListener("input", (e) => { - const profile = $control.value, custom = e.target.value.trim(); + let profile = $control.value, custom = e.target.value.trim(); UserAgent.updateStorage(profile, custom), this.onGlobalSettingChanged(e); }), $control.insertAdjacentElement("afterend", $inpCustomUserAgent), setNearby($inpCustomUserAgent.parentElement, { orientation: "vertical" @@ -3375,20 +3351,20 @@ class SettingsNavigationDialog extends NavigationDialog { }, ($parent) => { try { - const appVersion = document.querySelector("meta[name=gamepass-app-version]").content, appDate = new Date(document.querySelector("meta[name=gamepass-app-date]").content).toISOString().substring(0, 10); + let appVersion = document.querySelector("meta[name=gamepass-app-version]").content, appDate = new Date(document.querySelector("meta[name=gamepass-app-date]").content).toISOString().substring(0, 10); $parent.appendChild(CE("div", { class: "bx-settings-app-version" }, `xCloud website version ${appVersion} (${appDate})`)); } catch (e) {} }, ($parent) => { - const debugInfo = deepClone(BX_FLAGS.DeviceInfo); + let debugInfo = deepClone(BX_FLAGS.DeviceInfo); debugInfo.settings = JSON.parse(window.localStorage.getItem("better_xcloud") || "{}"); - const $debugInfo = CE("div", { class: "bx-debug-info" }, createButton({ + let $debugInfo = CE("div", { class: "bx-debug-info" }, createButton({ label: "Debug info", style: 4 | 64 | 32, onClick: (e) => { - const $pre = e.target.closest("button")?.nextElementSibling; + let $pre = e.target.closest("button")?.nextElementSibling; $pre.classList.toggle("bx-gone"), $pre.scrollIntoView(); } }), CE("pre", { @@ -3418,9 +3394,9 @@ class SettingsNavigationDialog extends NavigationDialog { disabled: !getPref("audio_enable_volume_control") }, onCreated: (setting, $elm) => { - const $range = $elm.querySelector("input[type=range"); + let $range = $elm.querySelector("input[type=range"); window.addEventListener(BxEvent.SETTINGS_CHANGED, (e) => { - const { storageKey, settingKey, settingValue } = e; + let { storageKey, settingKey, settingValue } = e; if (storageKey !== "better_xcloud" || settingKey !== "audio_volume") return; $range.value = settingValue, BxEvent.dispatch($range, "input", { ignoreOnChange: !0 @@ -3443,7 +3419,7 @@ class SettingsNavigationDialog extends NavigationDialog { }, { pref: "video_power_preference", onChange: () => { - const streamPlayer = STATES.currentStream.streamPlayer; + let streamPlayer = STATES.currentStream.streamPlayer; if (!streamPlayer) return; streamPlayer.reloadPlayer(), updateVideoPlayer(); } @@ -3517,7 +3493,7 @@ class SettingsNavigationDialog extends NavigationDialog { { pref: "stats_quick_glance", onChange: (e) => { - const streamStats = StreamStats.getInstance(); + let streamStats = StreamStats.getInstance(); e.target.checked ? streamStats.quickGlanceSetup() : streamStats.quickGlanceStop(); } }, @@ -3591,7 +3567,7 @@ class SettingsNavigationDialog extends NavigationDialog { onMounted() { if (!this.renderFullSettings) return; if (onChangeVideoPlayerType(), STATES.userAgent.capabilities.touch) BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED); - const $selectUserAgent = document.querySelector(`#bx_setting_${"user_agent_profile"}`); + let $selectUserAgent = document.querySelector(`#bx_setting_${"user_agent_profile"}`); if ($selectUserAgent) $selectUserAgent.disabled = !0, BxEvent.dispatch($selectUserAgent, "input", {}), $selectUserAgent.disabled = !1; } reloadPage() { @@ -3599,9 +3575,9 @@ class SettingsNavigationDialog extends NavigationDialog { } async getRecommendedSettings(deviceCode) { try { - const json = await (await NATIVE_FETCH(`https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/devices/${deviceCode.toLowerCase()}.json`)).json(), recommended = {}; + let json = await (await NATIVE_FETCH(`https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/devices/${deviceCode.toLowerCase()}.json`)).json(), recommended = {}; if (json.schema_version !== 1) return null; - const scriptSettings = json.settings.script; + let scriptSettings = json.settings.script; if (scriptSettings._base) { let base = typeof scriptSettings._base === "string" ? [scriptSettings._base] : scriptSettings._base; for (let profile of base) @@ -3634,7 +3610,7 @@ class SettingsNavigationDialog extends NavigationDialog { return requiredVariants = typeof requiredVariants === "string" ? [requiredVariants] : requiredVariants, requiredVariants.includes(SCRIPT_VARIANT); } async renderSuggestions(e) { - const $btnSuggest = e.target.closest("div"); + let $btnSuggest = e.target.closest("div"); $btnSuggest.toggleAttribute("bx-open"); let $content = $btnSuggest.nextElementSibling; if ($content) { @@ -3656,36 +3632,32 @@ class SettingsNavigationDialog extends NavigationDialog { let recommendedDevice = ""; if (BX_FLAGS.DeviceInfo.deviceType.includes("android")) { if (BX_FLAGS.DeviceInfo.androidInfo) { - const deviceCode = BX_FLAGS.DeviceInfo.androidInfo.board; + let deviceCode = BX_FLAGS.DeviceInfo.androidInfo.board; recommendedDevice = await this.getRecommendedSettings(deviceCode); } } - const hasRecommendedSettings = Object.keys(this.suggestedSettings.recommended).length > 0, deviceType = BX_FLAGS.DeviceInfo.deviceType; + let hasRecommendedSettings = Object.keys(this.suggestedSettings.recommended).length > 0, deviceType = BX_FLAGS.DeviceInfo.deviceType; if (deviceType === "android-handheld") this.addDefaultSuggestedSetting("stream_touch_controller", "off"), this.addDefaultSuggestedSetting("controller_device_vibration", "on"); else if (deviceType === "android") this.addDefaultSuggestedSetting("controller_device_vibration", "auto"); else if (deviceType === "android-tv") this.addDefaultSuggestedSetting("stream_touch_controller", "off"); this.generateDefaultSuggestedSettings(); - const $suggestedSettings = CE("div", { class: "bx-suggest-wrapper" }), $select = CE("select", {}, hasRecommendedSettings && CE("option", { value: "recommended" }, t("recommended")), !hasRecommendedSettings && CE("option", { value: "highest" }, t("highest-quality")), CE("option", { value: "default" }, t("default")), CE("option", { value: "lowest" }, t("lowest-quality"))); + let $suggestedSettings = CE("div", { class: "bx-suggest-wrapper" }), $select = CE("select", {}, hasRecommendedSettings && CE("option", { value: "recommended" }, t("recommended")), !hasRecommendedSettings && CE("option", { value: "highest" }, t("highest-quality")), CE("option", { value: "default" }, t("default")), CE("option", { value: "lowest" }, t("lowest-quality"))); $select.addEventListener("input", (e2) => { - const profile = $select.value; + let profile = $select.value; removeChildElements($suggestedSettings); - const fragment = document.createDocumentFragment(); - let note; + let fragment = document.createDocumentFragment(), note; if (profile === "recommended") note = t("recommended-settings-for-device", { device: recommendedDevice }); else if (profile === "highest") note = "⚠️ " + t("highest-quality-note"); note && fragment.appendChild(CE("div", { class: "bx-suggest-note" }, note)); - const settings = this.suggestedSettings[profile]; - let prefKey; + let settings = this.suggestedSettings[profile], prefKey; for (prefKey in settings) { - const currentValue = getPref(prefKey, !1), suggestedValue = settings[prefKey], currentValueText = STORAGE.Global.getValueText(prefKey, currentValue), isSameValue = currentValue === suggestedValue; - let $child, $value; + let currentValue = getPref(prefKey, !1), suggestedValue = settings[prefKey], currentValueText = STORAGE.Global.getValueText(prefKey, currentValue), isSameValue = currentValue === suggestedValue, $child, $value; if (isSameValue) $value = currentValueText; else { - const suggestedValueText = STORAGE.Global.getValueText(prefKey, suggestedValue); + let suggestedValueText = STORAGE.Global.getValueText(prefKey, suggestedValue); $value = currentValueText + " ➔ " + suggestedValueText; } - let $checkbox; - const breadcrumb = this.suggestedSettingLabels[prefKey] + " ❯ " + STORAGE.Global.getLabel(prefKey); + let $checkbox, breadcrumb = this.suggestedSettingLabels[prefKey] + " ❯ " + STORAGE.Global.getLabel(prefKey); if ($child = CE("div", { class: `bx-suggest-row ${isSameValue ? "bx-suggest-ok" : "bx-suggest-change"}` }, $checkbox = CE("input", { @@ -3705,13 +3677,12 @@ class SettingsNavigationDialog extends NavigationDialog { } $suggestedSettings.appendChild(fragment); }), BxEvent.dispatch($select, "input"); - const onClickApply = () => { - const profile = $select.value, settings = this.suggestedSettings[profile]; - let prefKey; + let onClickApply = () => { + let profile = $select.value, settings = this.suggestedSettings[profile], prefKey; for (prefKey in settings) { - const suggestedValue = settings[prefKey], $checkBox = $content.querySelector(`#bx_suggest_${prefKey}`); + let suggestedValue = settings[prefKey], $checkBox = $content.querySelector(`#bx_suggest_${prefKey}`); if (!$checkBox.checked || $checkBox.disabled) continue; - const $control = this.settingElements[prefKey]; + let $control = this.settingElements[prefKey]; if (!$control) { setPref(prefKey, suggestedValue); continue; @@ -3746,7 +3717,7 @@ class SettingsNavigationDialog extends NavigationDialog { }, t("suggest-settings-link"))), $btnSuggest?.insertAdjacentElement("afterend", $content); } renderTab(settingTab) { - const $svg = createSvgIcon(settingTab.icon); + let $svg = createSvgIcon(settingTab.icon); return $svg.dataset.group = settingTab.group, $svg.tabIndex = 0, $svg.addEventListener("click", (e) => { for (let $child of Array.from(this.$settings.children)) if ($child.getAttribute("data-tab-group") === settingTab.group) { @@ -3761,8 +3732,7 @@ class SettingsNavigationDialog extends NavigationDialog { this.$btnReload.classList.add("bx-danger"), this.$noteGlobalReload.classList.add("bx-gone"), this.$btnGlobalReload.classList.remove("bx-gone"), this.$btnGlobalReload.classList.add("bx-danger"); } renderServerSetting(setting) { - let selectedValue; - const $control = CE("select", { + let selectedValue, $control = CE("select", { id: `bx_setting_${setting.pref}`, title: setting.label, tabindex: 0 @@ -3771,15 +3741,14 @@ class SettingsNavigationDialog extends NavigationDialog { setPref(setting.pref, e.target.value), this.onGlobalSettingChanged(e); }), selectedValue = getPref("server_region"), setting.options = {}; for (let regionName in STATES.serverRegions) { - const region = STATES.serverRegions[regionName]; - let value = regionName, label = `${region.shortName} - ${regionName}`; + let region = STATES.serverRegions[regionName], value = regionName, label = `${region.shortName} - ${regionName}`; if (region.isDefault) { if (label += ` (${t("default")})`, value = "default", selectedValue === regionName) selectedValue = "default"; } setting.options[value] = label; } for (let value in setting.options) { - const label = setting.options[value], $option = CE("option", { value }, label); + let label = setting.options[value], $option = CE("option", { value }, label); $control.appendChild($option); } return $control.disabled = Object.keys(STATES.serverRegions).length === 0, $control.value = selectedValue, $control; @@ -3788,14 +3757,13 @@ class SettingsNavigationDialog extends NavigationDialog { if (typeof setting === "string") setting = { pref: setting }; - const pref = setting.pref; - let $control; + let pref = setting.pref, $control; if (setting.content) if (typeof setting.content === "function") $control = setting.content.apply(this); else $control = setting.content; else if (!setting.unsupported) { if (pref === "server_region") $control = this.renderServerSetting(setting); else if (pref === "bx_locale") $control = SettingElement.fromPref(pref, STORAGE.Global, async (e) => { - const newLocale = e.target.value; + let newLocale = e.target.value; if (getPref("ui_controller_friendly")) { let timeoutId = e.target.timeoutId; timeoutId && window.clearTimeout(timeoutId), e.target.timeoutId = window.setTimeout(() => { @@ -3805,10 +3773,9 @@ class SettingsNavigationDialog extends NavigationDialog { this.onGlobalSettingChanged(e); }); else if (pref === "user_agent_profile") $control = SettingElement.fromPref("user_agent_profile", STORAGE.Global, (e) => { - const value = e.target.value; - let isCustom = value === "custom", userAgent2 = UserAgent.get(value); + let value = e.target.value, isCustom = value === "custom", userAgent2 = UserAgent.get(value); UserAgent.updateStorage(value); - const $inp = $control.nextElementSibling; + let $inp = $control.nextElementSibling; $inp.value = userAgent2, $inp.readOnly = !isCustom, $inp.disabled = !isCustom, !e.target.disabled && this.onGlobalSettingChanged(e); }); else { @@ -3822,8 +3789,7 @@ class SettingsNavigationDialog extends NavigationDialog { let prefDefinition = null; if (pref) prefDefinition = getPrefDefinition(pref); if (prefDefinition && !this.isSupportedVariant(prefDefinition.requiredVariants)) return; - let label = prefDefinition?.label || setting.label, note = prefDefinition?.note || setting.note, unsupportedNote = prefDefinition?.unsupportedNote || setting.unsupportedNote; - const experimental = prefDefinition?.experimental || setting.experimental; + let label = prefDefinition?.label || setting.label, note = prefDefinition?.note || setting.note, unsupportedNote = prefDefinition?.unsupportedNote || setting.unsupportedNote, experimental = prefDefinition?.experimental || setting.experimental; if (settingTabContent.label && setting.pref) { if (prefDefinition?.suggest) typeof prefDefinition.suggest.lowest !== "undefined" && (this.suggestedSettings.lowest[setting.pref] = prefDefinition.suggest.lowest), typeof prefDefinition.suggest.highest !== "undefined" && (this.suggestedSettings.highest[setting.pref] = prefDefinition.suggest.highest); } @@ -3832,8 +3798,7 @@ class SettingsNavigationDialog extends NavigationDialog { let $note; if (unsupportedNote) $note = CE("div", { class: "bx-settings-dialog-note" }, unsupportedNote); else if (note) $note = CE("div", { class: "bx-settings-dialog-note" }, note); - let $label; - const $row = CE("label", { + let $label, $row = CE("label", { class: "bx-settings-row", for: `bx_setting_${pref}`, "data-type": settingTabContent.group, @@ -3847,8 +3812,7 @@ class SettingsNavigationDialog extends NavigationDialog { $tabContent.appendChild($row), !prefDefinition?.unsupported && setting.onCreated && setting.onCreated(setting, $control); } setupDialog() { - let $tabs, $settings; - const $container = CE("div", { + let $tabs, $settings, $container = CE("div", { class: "bx-settings-dialog", _nearby: { orientation: "horizontal" @@ -3900,9 +3864,9 @@ class SettingsNavigationDialog extends NavigationDialog { if (!settingTab) continue; if (!this.isSupportedVariant(settingTab.requiredVariants)) continue; if (settingTab.group !== "global" && !this.renderFullSettings) continue; - const $svg = this.renderTab(settingTab); + let $svg = this.renderTab(settingTab); $tabs.appendChild($svg); - const $tabContent = CE("div", { + let $tabContent = CE("div", { class: "bx-gone", "data-tab-group": settingTab.group }); @@ -3920,7 +3884,7 @@ class SettingsNavigationDialog extends NavigationDialog { }); } if (label) { - const $title = CE("h2", { + let $title = CE("h2", { _nearby: { orientation: "horizontal" } @@ -3933,7 +3897,7 @@ class SettingsNavigationDialog extends NavigationDialog { $tabContent.appendChild($title); } if (settingTabContent.unsupportedNote) { - const $note = CE("b", { class: "bx-note-unsupported" }, settingTabContent.unsupportedNote); + let $note = CE("b", { class: "bx-note-unsupported" }, settingTabContent.unsupportedNote); $tabContent.appendChild($note); } if (settingTabContent.unsupported) continue; @@ -3956,23 +3920,23 @@ class SettingsNavigationDialog extends NavigationDialog { $tabs.firstElementChild.dispatchEvent(new Event("click")); } focusTab(tabId) { - const $tab = this.$container.querySelector(`.bx-settings-tabs svg[data-group=${tabId}]`); + let $tab = this.$container.querySelector(`.bx-settings-tabs svg[data-group=${tabId}]`); $tab && $tab.dispatchEvent(new Event("click")); } focusIfNeeded() { this.jumpToSettingGroup("next"); } focusActiveTab() { - const $currentTab = this.$tabs.querySelector(".bx-active"); + let $currentTab = this.$tabs.querySelector(".bx-active"); return $currentTab && $currentTab.focus(), !0; } focusVisibleSetting(type = "first") { - const controls = Array.from(this.$settings.querySelectorAll("div[data-tab-group]:not(.bx-gone) > *")); + let controls = Array.from(this.$settings.querySelectorAll("div[data-tab-group]:not(.bx-gone) > *")); if (!controls.length) return !1; if (type === "last") controls.reverse(); for (let $control of controls) { if (!($control instanceof HTMLElement)) continue; - const $focusable = this.dialogManager.findFocusableElement($control); + let $focusable = this.dialogManager.findFocusableElement($control); if ($focusable) { if (this.dialogManager.focus($focusable)) return !0; } @@ -3980,7 +3944,7 @@ class SettingsNavigationDialog extends NavigationDialog { return !1; } focusVisibleTab(type = "first") { - const tabs = Array.from(this.$tabs.querySelectorAll("svg:not(.bx-gone)")); + let tabs = Array.from(this.$tabs.querySelectorAll("svg:not(.bx-gone)")); if (!tabs.length) return !1; if (type === "last") tabs.reverse(); for (let $tab of tabs) @@ -3988,14 +3952,12 @@ class SettingsNavigationDialog extends NavigationDialog { return !1; } jumpToSettingGroup(direction) { - const $tabContent = this.$settings.querySelector("div[data-tab-group]:not(.bx-gone)"); + let $tabContent = this.$settings.querySelector("div[data-tab-group]:not(.bx-gone)"); if (!$tabContent) return !1; - let $header; - const $focusing = document.activeElement; + let $header, $focusing = document.activeElement; if (!$focusing || !$tabContent.contains($focusing)) $header = $tabContent.querySelector("h2"); else { - const $parent = $focusing.closest("[data-tab-group] > *"), siblingProperty = direction === "next" ? "nextSibling" : "previousSibling"; - let $tmp = $parent, times = 0; + let $parent = $focusing.closest("[data-tab-group] > *"), siblingProperty = direction === "next" ? "nextSibling" : "previousSibling", $tmp = $parent, times = 0; while (!0) { if (!$tmp) break; if ($tmp.tagName === "H2") { @@ -4066,7 +4028,7 @@ var BxExposed = { $media.muted = !0; }); try { - const audioCtx = STATES.currentStream.audioContext, source = audioCtx.createMediaStreamSource(audioStream), gainNode = audioCtx.createGain(); + let audioCtx = STATES.currentStream.audioContext, source = audioCtx.createMediaStreamSource(audioStream), gainNode = audioCtx.createGain(); source.connect(gainNode).connect(audioCtx.destination); } catch (e) { BxLogger.error("setupGainNode", e), STATES.currentStream.audioGainNode = null; @@ -4081,9 +4043,9 @@ var BxExposed = { }, disableGamepadPolling: !1, backButtonPressed: () => { - const navigationDialogManager = NavigationDialogManager.getInstance(); + let navigationDialogManager = NavigationDialogManager.getInstance(); if (navigationDialogManager.isShowing()) return navigationDialogManager.hide(), !0; - const dict = { + let dict = { bubbles: !0, cancelable: !0, key: "XF86Back", @@ -4095,9 +4057,9 @@ var BxExposed = { } }; function localRedirect(path) { - const url = window.location.href.substring(0, 31) + path, $pageContent = document.getElementById("PageContent"); + let url = window.location.href.substring(0, 31) + path, $pageContent = document.getElementById("PageContent"); if (!$pageContent) return; - const $anchor = CE("a", { + let $anchor = CE("a", { href: url, class: "bx-hidden bx-offscreen" }, ""); @@ -4113,7 +4075,7 @@ function getPreferredServerRegion(shortName = !1) { if (preferredRegion in STATES.serverRegions) if (shortName && STATES.serverRegions[preferredRegion].shortName) return STATES.serverRegions[preferredRegion].shortName; else return preferredRegion; for (let regionName in STATES.serverRegions) { - const region = STATES.serverRegions[regionName]; + let region = STATES.serverRegions[regionName]; if (!region.isDefault) continue; if (shortName && region.shortName) return region.shortName; else return regionName; @@ -4143,7 +4105,7 @@ class HeaderSection { static #timeout; static #injectSettingsButton($parent) { if (!$parent) return; - const PREF_LATEST_VERSION = getPref("version_latest"), $btnSettings = HeaderSection.#$settingsBtn; + let PREF_LATEST_VERSION = getPref("version_latest"), $btnSettings = HeaderSection.#$settingsBtn; if (isElementVisible(HeaderSection.#$buttonsWrapper)) return; if ($btnSettings.querySelector("span").textContent = getPreferredServerRegion(!0) || t("better-xcloud"), !SCRIPT_VERSION.includes("beta") && PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) $btnSettings.setAttribute("data-update-available", "true"); $parent.appendChild(HeaderSection.#$buttonsWrapper); @@ -4157,7 +4119,7 @@ class HeaderSection { HeaderSection.#$remotePlayBtn.classList.remove("bx-gone"); } static watchHeader() { - const $root = document.querySelector("#PageContent header") || document.querySelector("#root"); + let $root = document.querySelector("#PageContent header") || document.querySelector("#root"); if (!$root) return; HeaderSection.#timeout && clearTimeout(HeaderSection.#timeout), HeaderSection.#timeout = null, HeaderSection.#observer && HeaderSection.#observer.disconnect(), HeaderSection.#observer = new MutationObserver((mutationList) => { HeaderSection.#timeout && clearTimeout(HeaderSection.#timeout), HeaderSection.#timeout = window.setTimeout(HeaderSection.checkHeader, 2000); @@ -4179,22 +4141,21 @@ class RemotePlayNavigationDialog extends NavigationDialog { this.setupDialog(); } setupDialog() { - const $fragment = CE("div", { class: "bx-remote-play-container" }), $settingNote = CE("p", {}), currentResolution = getPref("xhome_resolution"); - let $resolutions = CE("select", {}, CE("option", { value: "1080p" }, "1080p"), CE("option", { value: "720p" }, "720p")); + let $fragment = CE("div", { class: "bx-remote-play-container" }), $settingNote = CE("p", {}), currentResolution = getPref("xhome_resolution"), $resolutions = CE("select", {}, CE("option", { value: "1080p" }, "1080p"), CE("option", { value: "720p" }, "720p")); if (getPref("ui_controller_friendly")) $resolutions = BxSelectElement.wrap($resolutions); $resolutions.addEventListener("input", (e) => { - const value = e.target.value; + let value = e.target.value; $settingNote.textContent = value === "1080p" ? "✅ " + t("can-stream-xbox-360-games") : "❌ " + t("cant-stream-xbox-360-games"), setPref("xhome_resolution", value); }), $resolutions.value = currentResolution, BxEvent.dispatch($resolutions, "input", { manualTrigger: !0 }); - const $qualitySettings = CE("div", { + let $qualitySettings = CE("div", { class: "bx-remote-play-settings" }, CE("div", {}, CE("label", {}, t("target-resolution"), $settingNote), $resolutions)); $fragment.appendChild($qualitySettings); - const manager = RemotePlayManager.getInstance(), consoles = manager.getConsoles(); + let manager = RemotePlayManager.getInstance(), consoles = manager.getConsoles(); for (let con of consoles) { - const $child = CE("div", { class: "bx-remote-play-device-wrapper" }, CE("div", { class: "bx-remote-play-device-info" }, CE("div", {}, CE("span", { class: "bx-remote-play-device-name" }, con.deviceName), CE("span", { class: "bx-remote-play-console-type" }, con.consoleType.replace("Xbox", ""))), CE("div", { class: "bx-remote-play-power-state" }, this.STATE_LABELS[con.powerState])), createButton({ + let $child = CE("div", { class: "bx-remote-play-device-wrapper" }, CE("div", { class: "bx-remote-play-device-info" }, CE("div", {}, CE("span", { class: "bx-remote-play-device-name" }, con.deviceName), CE("span", { class: "bx-remote-play-console-type" }, con.consoleType.replace("Xbox", ""))), CE("div", { class: "bx-remote-play-power-state" }, this.STATE_LABELS[con.powerState])), createButton({ classes: ["bx-remote-play-connect-button"], label: t("console-connect"), style: 1 | 32, @@ -4225,7 +4186,7 @@ class RemotePlayNavigationDialog extends NavigationDialog { return this.$container; } focusIfNeeded() { - const $btnConnect = this.$container.querySelector(".bx-remote-play-device-wrapper button"); + let $btnConnect = this.$container.querySelector(".bx-remote-play-device-wrapper button"); $btnConnect && $btnConnect.focus(); } } @@ -4268,9 +4229,9 @@ class RemotePlayManager { GSSV_TOKEN = JSON.parse(localStorage.getItem("xboxcom_xbl_user_info")).tokens["http://gssv.xboxlive.com/"].token; } catch (e) { for (let i = 0;i < localStorage.length; i++) { - const key = localStorage.key(i); + let key = localStorage.key(i); if (!key.startsWith("Auth.User.")) continue; - const json = JSON.parse(localStorage.getItem(key)); + let json = JSON.parse(localStorage.getItem(key)); for (let token of json.tokens) { if (!token.relyingParty.includes("gssv.xboxlive.com")) continue; GSSV_TOKEN = token.tokenData.token; @@ -4279,7 +4240,7 @@ class RemotePlayManager { break; } } - const request = new Request("https://xhome.gssv-play-prod.xboxlive.com/v2/login/user", { + let request = new Request("https://xhome.gssv-play-prod.xboxlive.com/v2/login/user", { method: "POST", body: JSON.stringify({ offeringId: "xhome", @@ -4298,7 +4259,7 @@ class RemotePlayManager { callback(); return; } - const options = { + let options = { method: "GET", headers: { Authorization: `Bearer ${this.XHOME_TOKEN}` @@ -4306,7 +4267,7 @@ class RemotePlayManager { }; for (let region of this.regions) try { - const request = new Request(`${region.baseUri}/v6/servers/home?mr=50`, options), json = await (await fetch(request)).json(); + let request = new Request(`${region.baseUri}/v6/servers/home?mr=50`, options), json = await (await fetch(request)).json(); if (json.results.length === 0) continue; this.consoles = json.results, STATES.remotePlay.server = region.baseUri; break; @@ -4350,14 +4311,14 @@ class LoadingScreen { static waitTimeInterval = null; static orgWebTitle; static secondsToString(seconds) { - const m = Math.floor(seconds / 60), s = Math.floor(seconds % 60), mDisplay = m > 0 ? `${m}m` : "", sDisplay = `${s}s`.padStart(s >= 0 ? 3 : 4, "0"); + let m = Math.floor(seconds / 60), s = Math.floor(seconds % 60), mDisplay = m > 0 ? `${m}m` : "", sDisplay = `${s}s`.padStart(s >= 0 ? 3 : 4, "0"); return mDisplay + sDisplay; } static setup() { - const titleInfo = STATES.currentStream.titleInfo; + let titleInfo = STATES.currentStream.titleInfo; if (!titleInfo) return; if (!LoadingScreen.$bgStyle) { - const $bgStyle = CE("style"); + let $bgStyle = CE("style"); document.documentElement.appendChild($bgStyle), LoadingScreen.$bgStyle = $bgStyle; } if (LoadingScreen.setBackground(titleInfo.product.heroImageUrl || titleInfo.product.titledHeroImageUrl || titleInfo.product.tileImageUrl), getPref("ui_loading_screen_rocket") === "hide") LoadingScreen.hideRocket(); @@ -4369,7 +4330,7 @@ class LoadingScreen { static setBackground(imageUrl) { let $bgStyle = LoadingScreen.$bgStyle; imageUrl = imageUrl + "?w=1920", $bgStyle.textContent += '#game-stream{background-color:transparent !important;background-position:center center !important;background-repeat:no-repeat !important;background-size:cover !important}#game-stream rect[width="800"]{transition:opacity .3s ease-in-out !important}' + `#game-stream {background-image: linear-gradient(#00000033, #000000e6), url(${imageUrl}) !important;}`; - const bg = new Image; + let bg = new Image; bg.onload = (e) => { $bgStyle.textContent += '#game-stream rect[width="800"]{opacity:0 !important}'; }, bg.src = imageUrl; @@ -4378,7 +4339,7 @@ class LoadingScreen { if (getPref("ui_loading_screen_rocket") === "hide-queue") LoadingScreen.hideRocket(); let secondsLeft = waitTime, $countDown, $estimated; LoadingScreen.orgWebTitle = document.title; - const endDate = new Date, timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60; + let endDate = new Date, timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60; endDate.setSeconds(endDate.getSeconds() + waitTime - timeZoneOffsetSeconds); let endDateStr = endDate.toISOString().slice(0, 19); endDateStr = endDateStr.substring(0, 10) + " " + endDateStr.substring(11, 19), endDateStr += ` (${LoadingScreen.secondsToString(waitTime)})`; @@ -4391,7 +4352,7 @@ class LoadingScreen { } static hide() { if (LoadingScreen.orgWebTitle && (document.title = LoadingScreen.orgWebTitle), LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add("bx-gone"), getPref("ui_loading_screen_game_art") && LoadingScreen.$bgStyle) { - const $rocketBg = document.querySelector('#game-stream rect[width="800"]'); + let $rocketBg = document.querySelector('#game-stream rect[width="800"]'); $rocketBg && $rocketBg.addEventListener("transitionend", (e) => { LoadingScreen.$bgStyle.textContent += "#game-stream{background:#000 !important}"; }), LoadingScreen.$bgStyle.textContent += '#game-stream rect[width="800"]{opacity:1 !important}'; @@ -4455,12 +4416,12 @@ class GuideMenu { window.BX_EXPOSED.dialogRoutes.closeAll(); return; } - const $btnClose = document.querySelector("#gamepass-dialog-root button[class^=Header-module__closeButton]"); + let $btnClose = document.querySelector("#gamepass-dialog-root button[class^=Header-module__closeButton]"); $btnClose && $btnClose.click(); } static #renderButtons() { if (GuideMenu.#$renderedButtons) return GuideMenu.#$renderedButtons; - const $div = CE("div", { + let $div = CE("div", { class: "bx-guide-home-buttons" }), buttons = [ GuideMenu.#BUTTONS.scriptSettings, @@ -4474,7 +4435,7 @@ class GuideMenu { if (!$button) continue; if ($button instanceof HTMLElement) $div.appendChild($button); else if (Array.isArray($button)) { - const $wrapper = CE("div", {}); + let $wrapper = CE("div", {}); for (let $child of $button) $child && $wrapper.appendChild($child); $div.appendChild($wrapper); @@ -4486,19 +4447,19 @@ class GuideMenu { let $target = null; if (isPlaying) { $target = $root.querySelector("a[class*=QuitGameButton]"); - const $btnXcloudHome = $root.querySelector("div[class^=HomeButtonWithDivider]"); + let $btnXcloudHome = $root.querySelector("div[class^=HomeButtonWithDivider]"); $btnXcloudHome && ($btnXcloudHome.style.display = "none"); } else { - const $dividers = $root.querySelectorAll("div[class*=Divider-module__divider]"); + let $dividers = $root.querySelectorAll("div[class*=Divider-module__divider]"); if ($dividers) $target = $dividers[$dividers.length - 1]; } if (!$target) return !1; - const $buttons = GuideMenu.#renderButtons(); + let $buttons = GuideMenu.#renderButtons(); $buttons.dataset.isPlaying = isPlaying.toString(), $target.insertAdjacentElement("afterend", $buttons); } static async#onShown(e) { if (e.where === "home") { - const $root = document.querySelector("#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]"); + let $root = document.querySelector("#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]"); $root && GuideMenu.#injectHome($root, STATES.isPlaying); } } @@ -4506,9 +4467,9 @@ class GuideMenu { window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, GuideMenu.#onShown); } static observe($addedElm) { - const className = $addedElm.className; + let className = $addedElm.className; if (!className.startsWith("NavigationAnimation") && !className.startsWith("DialogRoutes") && !className.startsWith("Dialog-module__container")) return; - const $selectedTab = $addedElm.querySelector("div[class^=NavigationMenu] button[aria-selected=true"); + let $selectedTab = $addedElm.querySelector("div[class^=NavigationMenu] button[aria-selected=true"); if ($selectedTab) { let $elm = $selectedTab, index; for (index = 0;$elm = $elm?.previousElementSibling; index++) @@ -4568,8 +4529,7 @@ class StreamBadges { }; } renderBadge(name, value) { - const badgeInfo = this.badges[name]; - let $badge; + let badgeInfo = this.badges[name], $badge; if (badgeInfo.$element) return $badge = badgeInfo.$element, $badge.lastElementChild.textContent = value, $badge; if ($badge = CE("div", { class: "bx-badge", title: badgeInfo.name }, CE("span", { class: "bx-badge-name" }, createSvgIcon(badgeInfo.icon)), CE("span", { class: "bx-badge-value", style: `background-color: ${badgeInfo.color}` }, value)), name === "battery") $badge.classList.add("bx-badge-battery"); return this.badges[name].$element = $badge, $badge; @@ -4579,19 +4539,18 @@ class StreamBadges { this.stop(); return; } - const statsCollector = StreamStatsCollector.getInstance(); + let statsCollector = StreamStatsCollector.getInstance(); await statsCollector.collect(); - const play = statsCollector.getStat("play"), batt = statsCollector.getStat("batt"), dl = statsCollector.getStat("dl"), ul = statsCollector.getStat("ul"), badges = { + let play = statsCollector.getStat("play"), batt = statsCollector.getStat("batt"), dl = statsCollector.getStat("dl"), ul = statsCollector.getStat("ul"), badges = { download: dl.toString(), upload: ul.toString(), playtime: play.toString(), battery: batt.toString() - }; - let name; + }, name; for (name in badges) { - const value = badges[name]; + let value = badges[name]; if (value === null) continue; - const $elm = this.badges[name].$element; + let $elm = this.badges[name].$element; if (!$elm) continue; if ($elm.lastElementChild.textContent = value, name === "battery") if (batt.current === 100 && batt.start === 100) $elm.classList.add("bx-gone"); else $elm.dataset.charging = batt.isCharging.toString(), $elm.classList.remove("bx-gone"); @@ -4608,7 +4567,7 @@ class StreamBadges { await this.getServerStats(); let batteryLevel = ""; if (STATES.browser.capabilities.batteryApi) batteryLevel = "100%"; - const BADGES = [ + let BADGES = [ ["playtime", "1m"], ["battery", batteryLevel], ["download", humanFileSize(0)], @@ -4626,15 +4585,10 @@ class StreamBadges { }), this.$container = $container, await this.start(), $container; } async getServerStats() { - const stats = await STATES.currentStream.peerConnection.getStats(), allVideoCodecs = {}; - let videoCodecId, videoWidth = 0, videoHeight = 0; - const allAudioCodecs = {}; - let audioCodecId; - const allCandidates = {}; - let candidateId; + let stats = await STATES.currentStream.peerConnection.getStats(), allVideoCodecs = {}, videoCodecId, videoWidth = 0, videoHeight = 0, allAudioCodecs = {}, audioCodecId, allCandidates = {}, candidateId; if (stats.forEach((stat) => { if (stat.type === "codec") { - const mimeType = stat.mimeType.split("/")[0]; + let mimeType = stat.mimeType.split("/")[0]; if (mimeType === "video") allVideoCodecs[stat.id] = stat; else if (mimeType === "audio") allAudioCodecs[stat.id] = stat; } else if (stat.type === "inbound-rtp" && stat.packetsReceived > 0) { @@ -4643,19 +4597,18 @@ class StreamBadges { } else if (stat.type === "candidate-pair" && stat.packetsReceived > 0 && stat.state === "succeeded") candidateId = stat.remoteCandidateId; else if (stat.type === "remote-candidate") allCandidates[stat.id] = stat.address; }), videoCodecId) { - const videoStat = allVideoCodecs[videoCodecId], video = { + let videoStat = allVideoCodecs[videoCodecId], video = { width: videoWidth, height: videoHeight, codec: videoStat.mimeType.substring(6) }; if (video.codec === "H264") { - const match = /profile-level-id=([0-9a-f]{6})/.exec(videoStat.sdpFmtpLine); + let match = /profile-level-id=([0-9a-f]{6})/.exec(videoStat.sdpFmtpLine); match && (video.profile = match[1]); } let text = videoHeight + "p"; if (text && (text += "/"), text += video.codec, video.profile) { - const profile = video.profile; - let quality = profile; + let profile = video.profile, quality = profile; if (profile.startsWith("4d")) quality = t("visual-quality-high"); else if (profile.startsWith("42e")) quality = t("visual-quality-normal"); else if (profile.startsWith("420")) quality = t("visual-quality-low"); @@ -4664,7 +4617,7 @@ class StreamBadges { this.badges.video.$element = this.renderBadge("video", text), this.serverInfo.video = video; } if (audioCodecId) { - const audioStat = allAudioCodecs[audioCodecId], audio = { + let audioStat = allAudioCodecs[audioCodecId], audio = { codec: audioStat.mimeType.substring(6), bitrate: audioStat.clockRate }, bitrate = audio.bitrate / 1000, text = `${audio.codec} (${bitrate} kHz)`; @@ -4672,7 +4625,7 @@ class StreamBadges { } if (candidateId) { BxLogger.info("candidate", candidateId, allCandidates); - const server = this.serverInfo.server; + let server = this.serverInfo.server; if (server) { server.ipv6 = allCandidates[candidateId].includes(":"); let text = ""; @@ -4684,7 +4637,7 @@ class StreamBadges { static setupEvents() { window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, async (e) => { if (e.where !== "home" || !STATES.isPlaying) return; - const $btnQuit = document.querySelector("#gamepass-dialog-root a[class*=QuitGameButton]"); + let $btnQuit = document.querySelector("#gamepass-dialog-root a[class*=QuitGameButton]"); if ($btnQuit) $btnQuit.insertAdjacentElement("beforebegin", await StreamBadges.getInstance().render()); }); } @@ -4707,19 +4660,18 @@ class XcloudInterceptor { WestUS2: "🇺🇸" }; static async handleLogin(request, init) { - const bypassServer = getPref("server_bypass_restriction"); + let bypassServer = getPref("server_bypass_restriction"); if (bypassServer !== "off") { - const ip = BypassServerIps[bypassServer]; + let ip = BypassServerIps[bypassServer]; ip && request.headers.set("X-Forwarded-For", ip); } - const response = await NATIVE_FETCH(request, init); + let response = await NATIVE_FETCH(request, init); if (response.status !== 200) return BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE), response; - const obj = await response.clone().json(); + let obj = await response.clone().json(); RemotePlayManager.getInstance().xcloudToken = obj.gsToken; - const serverRegex = /\/\/(\w+)\./, serverEmojis = XcloudInterceptor.SERVER_EMOJIS; + let serverRegex = /\/\/(\w+)\./, serverEmojis = XcloudInterceptor.SERVER_EMOJIS; for (let region of obj.offeringSettings.regions) { - const regionName = region.name; - let shortName = region.name; + let { name: regionName, name: shortName } = region; if (region.isDefault) STATES.selectedRegion = Object.assign({}, region); let match = serverRegex.exec(region.baseUri); if (match) { @@ -4728,49 +4680,47 @@ class XcloudInterceptor { region.shortName = shortName.toUpperCase(), STATES.serverRegions[region.name] = Object.assign({}, region); } BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_READY); - const preferredRegion = getPreferredServerRegion(); + let preferredRegion = getPreferredServerRegion(); if (preferredRegion && preferredRegion in STATES.serverRegions) { - const tmp = Object.assign({}, STATES.serverRegions[preferredRegion]); + let tmp = Object.assign({}, STATES.serverRegions[preferredRegion]); tmp.isDefault = !0, obj.offeringSettings.regions = [tmp], STATES.selectedRegion = tmp; } return STATES.gsToken = obj.gsToken, response.json = () => Promise.resolve(obj), response; } static async handlePlay(request, init) { - const PREF_STREAM_TARGET_RESOLUTION = getPref("stream_target_resolution"), PREF_STREAM_PREFERRED_LOCALE = getPref("stream_preferred_locale"), url = typeof request === "string" ? request : request.url, parsedUrl = new URL(url); - let badgeRegion = parsedUrl.host.split(".", 1)[0]; + let PREF_STREAM_TARGET_RESOLUTION = getPref("stream_target_resolution"), PREF_STREAM_PREFERRED_LOCALE = getPref("stream_preferred_locale"), url = typeof request === "string" ? request : request.url, parsedUrl = new URL(url), badgeRegion = parsedUrl.host.split(".", 1)[0]; for (let regionName in STATES.serverRegions) { - const region = STATES.serverRegions[regionName]; + let region = STATES.serverRegions[regionName]; if (parsedUrl.origin == region.baseUri) { badgeRegion = regionName; break; } } StreamBadges.getInstance().setRegion(badgeRegion); - const body = await request.clone().json(); + let body = await request.clone().json(); if (PREF_STREAM_TARGET_RESOLUTION !== "auto") { - const osName = PREF_STREAM_TARGET_RESOLUTION === "720p" ? "android" : "windows"; + let osName = PREF_STREAM_TARGET_RESOLUTION === "720p" ? "android" : "windows"; body.settings.osName = osName; } if (PREF_STREAM_PREFERRED_LOCALE !== "default") body.settings.locale = PREF_STREAM_PREFERRED_LOCALE; - const newRequest = new Request(request, { + let newRequest = new Request(request, { body: JSON.stringify(body) }); return NATIVE_FETCH(newRequest); } static async handleWaitTime(request, init) { - const response = await NATIVE_FETCH(request, init); + let response = await NATIVE_FETCH(request, init); if (getPref("ui_loading_screen_wait_time")) { - const json = await response.clone().json(); + let json = await response.clone().json(); if (json.estimatedAllocationTimeInSeconds > 0) LoadingScreen.setupWaitTime(json.estimatedTotalWaitTimeInSeconds); } return response; } static async handleConfiguration(request, init) { if (request.method !== "GET") return NATIVE_FETCH(request, init); - const response = await NATIVE_FETCH(request, init), text = await response.clone().text(); + let response = await NATIVE_FETCH(request, init), text = await response.clone().text(); if (!text.length) return response; - const obj = JSON.parse(text); - let overrides = JSON.parse(obj.clientStreamingConfigOverrides || "{}") || {}; + let obj = JSON.parse(text), overrides = JSON.parse(obj.clientStreamingConfigOverrides || "{}") || {}; overrides.inputConfiguration = overrides.inputConfiguration || {}, overrides.inputConfiguration.enableVibration = !0; let overrideMkb = null; if (getPref("native_mkb_enabled") === "on" || STATES.currentStream.titleInfo && BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo.details.productId)) overrideMkb = !0; @@ -4796,11 +4746,11 @@ function clearApplicationInsightsBuffers() { window.sessionStorage.removeItem("AI_buffer"), window.sessionStorage.removeItem("AI_sentBuffer"); } function clearDbLogs(dbName, table) { - const request = window.indexedDB.open(dbName); + let request = window.indexedDB.open(dbName); request.onsuccess = (e) => { - const db = e.target.result; + let db = e.target.result; try { - const objectStoreRequest = db.transaction(table, "readwrite").objectStore(table).clear(); + let objectStoreRequest = db.transaction(table, "readwrite").objectStore(table).clear(); objectStoreRequest.onsuccess = function() { console.log(`[Better xCloud] Cleared ${dbName}.${table}`); }; @@ -4811,19 +4761,17 @@ function clearAllLogs() { clearApplicationInsightsBuffers(), clearDbLogs("StreamClientLogHandler", "logs"), clearDbLogs("XCloudAppLogs", "logs"); } function updateIceCandidates(candidates, options) { - const pattern = new RegExp(/a=candidate:(?\d+) (?\d+) UDP (?\d+) (?[^\s]+) (?\d+) (?.*)/), lst = []; + let pattern = new RegExp(/a=candidate:(?\d+) (?\d+) UDP (?\d+) (?[^\s]+) (?\d+) (?.*)/), lst = []; for (let item of candidates) { if (item.candidate == "a=end-of-candidates") continue; - const groups = pattern.exec(item.candidate).groups; + let groups = pattern.exec(item.candidate).groups; lst.push(groups); } if (options.preferIpv6Server) lst.sort((a, b) => { - const firstIp = a.ip, secondIp = b.ip; + let firstIp = a.ip, secondIp = b.ip; return !firstIp.includes(":") && secondIp.includes(":") ? 1 : -1; }); - const newCandidates = []; - let foundation = 1; - const newCandidate = (candidate) => { + let newCandidates = [], foundation = 1, newCandidate = (candidate) => { return { candidate, messageType: "iceCandidate", @@ -4840,13 +4788,12 @@ function updateIceCandidates(candidates, options) { return newCandidates.push(newCandidate("a=end-of-candidates")), BxLogger.info("ICE Candidates", newCandidates), newCandidates; } async function patchIceCandidates(request, consoleAddrs) { - const response = await NATIVE_FETCH(request), text = await response.clone().text(); + let response = await NATIVE_FETCH(request), text = await response.clone().text(); if (!text.length) return response; - const options = { + let options = { preferIpv6Server: getPref("prefer_ipv6_server"), consoleAddrs - }, obj = JSON.parse(text); - let exchangeResponse = JSON.parse(obj.exchangeResponse); + }, obj = JSON.parse(text), exchangeResponse = JSON.parse(obj.exchangeResponse); return exchangeResponse = updateIceCandidates(exchangeResponse, options), obj.exchangeResponse = JSON.stringify(exchangeResponse), response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response; } function interceptHttpRequests() { @@ -4862,7 +4809,7 @@ function interceptHttpRequests() { "https://peoplehub.xboxlive.com/users/me/people/recommendations", "https://xblmessaging.xboxlive.com/network/xbox/users/me/inbox" ]); - const xhrPrototype = XMLHttpRequest.prototype, nativeXhrOpen = xhrPrototype.open, nativeXhrSend = xhrPrototype.send; + let xhrPrototype = XMLHttpRequest.prototype, nativeXhrOpen = xhrPrototype.open, nativeXhrSend = xhrPrototype.send; xhrPrototype.open = function(method, url) { return this._url = url, nativeXhrOpen.apply(this, arguments); }, xhrPrototype.send = function(...arg) { @@ -4886,7 +4833,7 @@ function interceptHttpRequests() { if (url.endsWith("/play")) BxEvent.dispatch(window, BxEvent.STREAM_LOADING); if (url.endsWith("/configuration")) BxEvent.dispatch(window, BxEvent.STREAM_STARTING); if (url.startsWith("https://emerald.xboxservices.com/xboxcomfd/experimentation")) try { - const response = await NATIVE_FETCH(request, init), json = await response.json(); + let response = await NATIVE_FETCH(request, init), json = await response.json(); if (json && json.exp && json.exp.treatments) for (let key in FeatureGates) json.exp.treatments[key] = FeatureGates[key]; return response.json = () => Promise.resolve(json), response; @@ -4894,16 +4841,16 @@ function interceptHttpRequests() { console.log(e); } if (STATES.userAgent.capabilities.touch && url.includes("catalog.gamepass.com/sigls/")) { - const response = await NATIVE_FETCH(request, init), obj = await response.clone().json(); + let response = await NATIVE_FETCH(request, init), obj = await response.clone().json(); if (url.includes("29a81209-df6f-41fd-a528-2ae6b91f719c")) for (let i = 1;i < obj.length; i++) gamepassAllGames.push(obj[i].id); else if (!1) try {} catch (e) {} return response.json = () => Promise.resolve(obj), response; } if (BX_FLAGS.ForceNativeMkbTitles && url.includes("catalog.gamepass.com/sigls/") && url.includes("8fa264dd-124f-4af3-97e8-596fcdf4b486")) { - const response = await NATIVE_FETCH(request, init), obj = await response.clone().json(); + let response = await NATIVE_FETCH(request, init), obj = await response.clone().json(); try { - const newCustomList = BX_FLAGS.ForceNativeMkbTitles.map((item) => ({ id: item })); + let newCustomList = BX_FLAGS.ForceNativeMkbTitles.map((item) => ({ id: item })); obj.push(...newCustomList); } catch (e) { console.log(e); @@ -4921,7 +4868,7 @@ function showGamepadToast(gamepad) { BxLogger.info("Gamepad", gamepad); let text = "🎮"; if (getPref("local_co_op_enabled")) text += ` #${gamepad.index + 1}`; - const gamepadId = gamepad.id.replace(/ \(.*?Vendor: \w+ Product: \w+\)$/, ""); + let gamepadId = gamepad.id.replace(/ \(.*?Vendor: \w+ Product: \w+\)$/, ""); text += ` - ${gamepadId}`; let status; if (gamepad.connected) status = (gamepad.vibrationActuator ? "✅" : "❌") + " " + t("vibration-status"); @@ -4929,8 +4876,7 @@ function showGamepadToast(gamepad) { Toast.show(text, status, { instant: !1 }); } function addCss() { - let css = `:root{--bx-title-font:Bahnschrift,Arial,Helvetica,sans-serif;--bx-title-font-semibold:Bahnschrift Semibold,Arial,Helvetica,sans-serif;--bx-normal-font:"Segoe UI",Arial,Helvetica,sans-serif;--bx-monospaced-font:Consolas,"Courier New",Courier,monospace;--bx-promptfont-font:promptfont;--bx-button-height:40px;--bx-default-button-color:#2d3036;--bx-default-button-rgb:45,48,54;--bx-default-button-hover-color:#515863;--bx-default-button-hover-rgb:81,88,99;--bx-default-button-active-color:#222428;--bx-default-button-active-rgb:34,36,40;--bx-default-button-disabled-color:#8e8e8e;--bx-default-button-disabled-rgb:142,142,142;--bx-primary-button-color:#008746;--bx-primary-button-rgb:0,135,70;--bx-primary-button-hover-color:#04b358;--bx-primary-button-hover-rgb:4,179,88;--bx-primary-button-active-color:#044e2a;--bx-primary-button-active-rgb:4,78,42;--bx-primary-button-disabled-color:#448262;--bx-primary-button-disabled-rgb:68,130,98;--bx-danger-button-color:#c10404;--bx-danger-button-rgb:193,4,4;--bx-danger-button-hover-color:#e61d1d;--bx-danger-button-hover-rgb:230,29,29;--bx-danger-button-active-color:#a26c6c;--bx-danger-button-active-rgb:162,108,108;--bx-danger-button-disabled-color:#df5656;--bx-danger-button-disabled-rgb:223,86,86;--bx-fullscreen-text-z-index:99999;--bx-toast-z-index:60000;--bx-dialog-z-index:50000;--bx-dialog-overlay-z-index:40200;--bx-stats-bar-z-index:40100;--bx-mkb-pointer-lock-msg-z-index:40000;--bx-navigation-dialog-z-index:30100;--bx-navigation-dialog-overlay-z-index:30000;--bx-game-bar-z-index:10000;--bx-screenshot-animation-z-index:9000;--bx-wait-time-box-z-index:1000}@font-face{font-family:'promptfont';src:url("https://redphx.github.io/better-xcloud/fonts/promptfont.otf")}div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module__hiddenContainer]){opacity:0;pointer-events:none !important;position:absolute;top:-9999px;left:-9999px}@media screen and (max-width:640px){header a[href="/play"]{display:none}}.bx-full-width{width:100% !important}.bx-full-height{height:100% !important}.bx-no-scroll{overflow:hidden !important}.bx-hide-scroll-bar{scrollbar-width:none}.bx-hide-scroll-bar::-webkit-scrollbar{display:none}.bx-gone{display:none !important}.bx-offscreen{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-hidden{visibility:hidden !important}.bx-invisible{opacity:0}.bx-unclickable{pointer-events:none}.bx-pixel{width:1px !important;height:1px !important}.bx-no-margin{margin:0 !important}.bx-no-padding{padding:0 !important}.bx-prompt{font-family:var(--bx-promptfont-font)}.bx-line-through{text-decoration:line-through !important}.bx-normal-case{text-transform:none !important}.bx-normal-link{text-transform:none !important;text-align:left !important;font-weight:400 !important;font-family:var(--bx-normal-font) !important}select[multiple]{overflow:auto}#headerArea,#uhfSkipToMain,.uhf-footer{display:none}div[class*=NotFocusedDialog]{position:absolute !important;top:-9999px !important;left:-9999px !important;width:0 !important;height:0 !important}#game-stream video:not([src]){visibility:hidden}div[class*=SupportedInputsBadge]:not(:has(:nth-child(2))),div[class*=SupportedInputsBadge] svg:first-of-type{display:none}.bx-game-tile-wait-time{position:absolute;top:0;left:0;z-index:1;background:rgba(0,0,0,0.549);display:flex;border-radius:4px 0 4px 0;align-items:center;padding:4px 8px}.bx-game-tile-wait-time svg{width:14px;height:16px;margin-right:2px}.bx-game-tile-wait-time span{display:inline-block;height:16px;line-height:16px;font-size:12px;font-weight:bold;margin-left:2px}.bx-fullscreen-text{position:fixed;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,0.8);z-index:var(--bx-fullscreen-text-z-index);line-height:100vh;color:#fff;text-align:center;font-weight:400;font-family:var(--bx-normal-font);font-size:1.3rem;user-select:none;-webkit-user-select:none}#root section[class*=DeviceCodePage-module__page]{margin-left:20px !important;margin-right:20px !important;margin-top:20px !important;max-width:800px !important}#root div[class*=DeviceCodePage-module__back]{display:none}.bx-button{--button-rgb:var(--bx-default-button-rgb);--button-hover-rgb:var(--bx-default-button-hover-rgb);--button-active-rgb:var(--bx-default-button-active-rgb);--button-disabled-rgb:var(--bx-default-button-disabled-rgb);background-color:rgb(var(--button-rgb));user-select:none;-webkit-user-select:none;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;border:none;font-weight:400;height:var(--bx-button-height);border-radius:4px;padding:0 8px;text-transform:uppercase;cursor:pointer;overflow:hidden}.bx-button:not([disabled]):active{background-color:rgb(var(--button-active-rgb))}.bx-button:focus{outline:none !important}.bx-button:not([disabled]):not(:active):hover,.bx-button:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button:disabled{cursor:default;background-color:rgb(var(--button-disabled-rgb))}.bx-button.bx-ghost{background-color:transparent}.bx-button.bx-ghost:not([disabled]):not(:active):hover,.bx-button.bx-ghost:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button.bx-primary{--button-rgb:var(--bx-primary-button-rgb)}.bx-button.bx-primary:not([disabled]):active{--button-active-rgb:var(--bx-primary-button-active-rgb)}.bx-button.bx-primary:not([disabled]):not(:active):hover,.bx-button.bx-primary:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-primary-button-hover-rgb)}.bx-button.bx-primary:disabled{--button-disabled-rgb:var(--bx-primary-button-disabled-rgb)}.bx-button.bx-danger{--button-rgb:var(--bx-danger-button-rgb)}.bx-button.bx-danger:not([disabled]):active{--button-active-rgb:var(--bx-danger-button-active-rgb)}.bx-button.bx-danger:not([disabled]):not(:active):hover,.bx-button.bx-danger:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-danger-button-hover-rgb)}.bx-button.bx-danger:disabled{--button-disabled-rgb:var(--bx-danger-button-disabled-rgb)}.bx-button.bx-frosted{--button-alpha:.2;background-color:rgba(var(--button-rgb), var(--button-alpha));backdrop-filter:blur(4px) brightness(1.5)}.bx-button.bx-frosted:not([disabled]):not(:active):hover,.bx-button.bx-frosted:not([disabled]):not(:active).bx-focusable:focus{background-color:rgba(var(--button-hover-rgb), var(--button-alpha))}.bx-button.bx-drop-shadow{box-shadow:0 0 4px rgba(0,0,0,0.502)}.bx-button.bx-tall{height:calc(var(--bx-button-height) * 1.5) !important}.bx-button.bx-circular{border-radius:var(--bx-button-height);height:var(--bx-button-height)}.bx-button svg{display:inline-block;width:16px;height:var(--bx-button-height)}.bx-button span{display:inline-block;line-height:var(--bx-button-height);vertical-align:middle;color:#fff;overflow:hidden;white-space:nowrap}.bx-button span:not(:only-child){margin-left:10px}.bx-focusable{position:relative;overflow:visible}.bx-focusable::after{border:2px solid transparent;border-radius:10px}.bx-focusable:focus::after{content:'';border-color:#fff;position:absolute;top:-6px;left:-6px;right:-6px;bottom:-6px}html[data-active-input=touch] .bx-focusable:focus::after,html[data-active-input=mouse] .bx-focusable:focus::after{border-color:transparent !important}.bx-focusable.bx-circular::after{border-radius:var(--bx-button-height)}a.bx-button{display:inline-block}a.bx-button.bx-full-width{text-align:center}button.bx-inactive{pointer-events:none;opacity:.2;background:transparent !important}.bx-header-remote-play-button{height:auto;margin-right:8px !important}.bx-header-remote-play-button svg{width:24px;height:24px}.bx-header-settings-button{line-height:30px;font-size:14px;text-transform:uppercase;position:relative}.bx-header-settings-button[data-update-available]::before{content:'🌟' !important;line-height:var(--bx-button-height);display:inline-block;margin-left:4px}.bx-dialog-overlay{position:fixed;inset:0;z-index:var(--bx-dialog-overlay-z-index);background:#000;opacity:50%}.bx-dialog{display:flex;flex-flow:column;max-height:90vh;position:fixed;top:50%;left:50%;margin-right:-50%;transform:translate(-50%,-50%);min-width:420px;padding:20px;border-radius:8px;z-index:var(--bx-dialog-z-index);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-normal-font);box-shadow:0 0 6px #000;user-select:none;-webkit-user-select:none}.bx-dialog *:focus{outline:none !important}.bx-dialog h2{display:flex;margin-bottom:12px}.bx-dialog h2 b{flex:1;color:#fff;display:block;font-family:var(--bx-title-font);font-size:26px;font-weight:400;line-height:var(--bx-button-height)}.bx-dialog.bx-binding-dialog h2 b{font-family:var(--bx-promptfont-font) !important}.bx-dialog > div{overflow:auto;padding:2px 0}.bx-dialog > button{padding:8px 32px;margin:10px auto 0;border:none;border-radius:4px;display:block;background-color:#2d3036;text-align:center;color:#fff;text-transform:uppercase;font-family:var(--bx-title-font);font-weight:400;line-height:18px;font-size:14px}@media (hover:hover){.bx-dialog > button:hover{background-color:#515863}}.bx-dialog > button:focus{background-color:#515863}@media screen and (max-width:450px){.bx-dialog{min-width:100%}}.bx-navigation-dialog{position:absolute;z-index:var(--bx-navigation-dialog-z-index);font-family:var(--bx-title-font)}.bx-navigation-dialog *:focus{outline:none !important}.bx-navigation-dialog-overlay{position:fixed;background:rgba(11,11,11,0.89);top:0;left:0;right:0;bottom:0;z-index:var(--bx-navigation-dialog-overlay-z-index)}.bx-navigation-dialog-overlay[data-is-playing="true"]{background:transparent}.bx-settings-dialog{display:flex;position:fixed;top:0;right:0;bottom:0;opacity:.98;user-select:none;-webkit-user-select:none}.bx-settings-dialog .bx-focusable::after{border-radius:4px}.bx-settings-dialog .bx-focusable:focus::after{top:0;left:0;right:0;bottom:0}.bx-settings-dialog .bx-settings-reload-note{font-size:.8rem;display:block;padding:8px;font-style:italic;font-weight:normal;height:var(--bx-button-height)}.bx-settings-dialog input{accent-color:var(--bx-primary-button-color)}.bx-settings-dialog input:focus{accent-color:var(--bx-danger-button-color)}.bx-settings-dialog select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;border:none;color:#fff}.bx-settings-dialog select option:disabled{display:none}.bx-settings-dialog input[type=checkbox]:focus,.bx-settings-dialog select:focus{filter:drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff)}.bx-settings-dialog a{color:#1c9d1c;text-decoration:none}.bx-settings-dialog a:hover,.bx-settings-dialog a:focus{color:#5dc21e}.bx-settings-tabs-container{position:fixed;width:48px;max-height:100vh;display:flex;flex-direction:column}.bx-settings-tabs-container > div:last-of-type{display:flex;flex-direction:column;align-items:end}.bx-settings-tabs-container > div:last-of-type button{flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0;margin-top:8px;height:unset;padding:8px 10px}.bx-settings-tabs-container > div:last-of-type button svg{width:16px;height:16px}.bx-settings-tabs{display:flex;flex-direction:column;border-radius:0 0 0 8px;box-shadow:0 0 6px #000;overflow:overlay;flex:1}.bx-settings-tabs svg{width:24px;height:24px;padding:10px;flex-shrink:0;box-sizing:content-box;background:#131313;cursor:pointer;border-left:4px solid #1e1e1e}.bx-settings-tabs svg.bx-active{background:#222;border-color:#008746}.bx-settings-tabs svg:not(.bx-active):hover{background:#2f2f2f;border-color:#484848}.bx-settings-tabs svg:focus{border-color:#fff}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]{background:var(--bx-danger-button-color) !important}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]:hover{background:var(--bx-danger-button-hover-color) !important}.bx-settings-tab-contents{flex-direction:column;padding:10px;margin-left:48px;width:450px;max-width:calc(100vw - tabsWidth);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-title-font);text-align:center;box-shadow:0 0 6px #000;overflow:overlay;z-index:1}.bx-settings-tab-contents > div[data-tab-group=mkb]{display:flex;flex-direction:column;height:100%;overflow:hidden}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=true] > div:first-of-type{display:none}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=true] > div:last-of-type{display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=false] > div:first-of-type{display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=false] > div:last-of-type{display:none}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-profile{width:100%;height:36px;display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-note{margin-top:10px;font-size:14px}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row{display:flex;margin-bottom:10px}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row label.bx-prompt{flex:1;font-size:26px;margin-bottom:0}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions{flex:2;position:relative}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions select{position:absolute;width:100%;height:100%;display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions select:last-of-type{opacity:0;z-index:calc(var(--bx-settings-z-index) + 1)}.bx-settings-tab-contents .bx-top-buttons{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}.bx-settings-tab-contents .bx-top-buttons .bx-button{display:block}.bx-settings-tab-contents h2{margin:16px 0 8px 0;display:flex;align-items:center}.bx-settings-tab-contents h2:first-of-type{margin-top:0}.bx-settings-tab-contents h2 span{display:inline-block;font-size:20px;font-weight:bold;text-align:left;flex:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}@media (max-width:500px){.bx-settings-tab-contents{width:calc(100vw - 48px)}}.bx-settings-row{display:flex;gap:10px;padding:16px 10px;margin:0;background:#2a2a2a;border-bottom:1px solid #343434}.bx-settings-row:hover,.bx-settings-row:focus-within{background-color:#242424}.bx-settings-row:not(:has(> input[type=checkbox])){flex-wrap:wrap}.bx-settings-row > span.bx-settings-label{font-size:14px;display:block;text-align:left;align-self:center;margin-bottom:0 !important;flex:1}.bx-settings-row > span.bx-settings-label + *{margin:0 0 0 auto}.bx-settings-dialog-note{display:block;color:#afafb0;font-size:12px;font-weight:lighter;font-style:italic}.bx-settings-dialog-note:not(:has(a)){margin-top:4px}.bx-settings-dialog-note a{display:inline-block;padding:4px}.bx-settings-custom-user-agent{display:block;width:100%;padding:6px}.bx-donation-link{display:block;text-align:center;text-decoration:none;height:20px;line-height:20px;font-size:14px;margin-top:10px}.bx-debug-info button{margin-top:10px}.bx-debug-info pre{margin-top:10px;cursor:copy;color:#fff;padding:8px;border:1px solid #2d2d2d;background:#212121;white-space:break-spaces;text-align:left}.bx-debug-info pre:hover{background:#272727}.bx-settings-app-version{margin-top:10px;text-align:center;color:#747474;font-size:12px}.bx-note-unsupported{display:block;font-size:12px;font-style:italic;font-weight:normal;color:#828282}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:has(+ .bx-settings-row){border-top-left-radius:10px;border-top-right-radius:10px}.bx-settings-tab-contents > div .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-bottom-left-radius:10px;border-bottom-right-radius:10px}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-radius:10px}.bx-suggest-toggler{text-align:left;display:flex;border-radius:4px;overflow:hidden;background:#003861}.bx-suggest-toggler label{flex:1;margin-bottom:0;padding:10px;background:#004f87}.bx-suggest-toggler span{display:inline-block;align-self:center;padding:10px;width:40px;text-align:center}.bx-suggest-toggler:hover,.bx-suggest-toggler:focus{cursor:pointer;background:#005da1}.bx-suggest-toggler:hover label,.bx-suggest-toggler:focus label{background:#006fbe}.bx-suggest-toggler[bx-open] span{transform:rotate(90deg)}.bx-suggest-toggler[bx-open]+ .bx-suggest-box{display:block}.bx-suggest-box{display:none;background:#161616;padding:10px;box-shadow:0 0 12px #0f0f0f inset;border-radius:10px}.bx-suggest-wrapper{display:flex;flex-direction:column;gap:10px;margin:10px}.bx-suggest-note{font-size:11px;color:#8c8c8c;font-style:italic;font-weight:100}.bx-suggest-link{font-size:14px;display:inline-block;margin-top:4px;padding:4px}.bx-suggest-row{display:flex;flex-direction:row;gap:10px}.bx-suggest-row label{flex:1;overflow:overlay;border-radius:4px}.bx-suggest-row label .bx-suggest-label{background:#323232;padding:4px 10px;font-size:12px;text-align:left}.bx-suggest-row label .bx-suggest-value{padding:6px;font-size:14px}.bx-suggest-row label .bx-suggest-value.bx-suggest-change{background-color:var(--bx-warning-color)}.bx-suggest-row.bx-suggest-ok input{visibility:hidden}.bx-suggest-row.bx-suggest-ok .bx-suggest-label{background-color:#008114}.bx-suggest-row.bx-suggest-ok .bx-suggest-value{background-color:#13a72a}.bx-suggest-row.bx-suggest-change .bx-suggest-label{background-color:#a65e08}.bx-suggest-row.bx-suggest-change .bx-suggest-value{background-color:#d57f18}.bx-suggest-row.bx-suggest-change:hover label{cursor:pointer}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-label{background-color:#995707}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-value{background-color:#bd7115}.bx-suggest-row.bx-suggest-change input:not(:checked) + label{opacity:.5}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-label{background-color:#2a2a2a}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-value{background-color:#393939}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label{opacity:1}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-label{background-color:#202020}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-value{background-color:#303030}.bx-toast{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:24px;transform:translate(-50%,0);background:#000;border-radius:16px;color:#fff;z-index:var(--bx-toast-z-index);font-family:var(--bx-normal-font);border:2px solid #fff;display:flex;align-items:center;opacity:0;overflow:clip;transition:opacity .2s ease-in}.bx-toast.bx-show{opacity:.85}.bx-toast.bx-hide{opacity:0;pointer-events:none}.bx-toast-msg{font-size:14px;display:inline-block;padding:12px 16px;white-space:pre}.bx-toast-status{font-weight:bold;font-size:14px;text-transform:uppercase;display:inline-block;background:#515863;padding:12px 16px;color:#fff;white-space:pre}.bx-wait-time-box{position:fixed;top:0;right:0;background-color:rgba(0,0,0,0.8);color:#fff;z-index:var(--bx-wait-time-box-z-index);padding:12px;border-radius:0 0 0 8px}.bx-wait-time-box label{display:block;text-transform:uppercase;text-align:right;font-size:12px;font-weight:bold;margin:0}.bx-wait-time-box span{display:block;font-family:var(--bx-monospaced-font);text-align:right;font-size:16px;margin-bottom:10px}.bx-wait-time-box span:last-of-type{margin-bottom:0}.bx-remote-play-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;width:420px;max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:20px}.bx-remote-play-container > .bx-button{display:table;margin:0 0 0 auto}.bx-remote-play-settings{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #2d2d2d}.bx-remote-play-settings > div{display:flex}.bx-remote-play-settings label{flex:1}.bx-remote-play-settings label p{margin:4px 0 0;padding:0;color:#888;font-size:12px}.bx-remote-play-resolution{display:block}.bx-remote-play-resolution input[type="radio"]{accent-color:var(--bx-primary-button-color);margin-right:6px}.bx-remote-play-resolution input[type="radio"]:focus{accent-color:var(--bx-primary-button-hover-color)}.bx-remote-play-device-wrapper{display:flex;margin-bottom:12px}.bx-remote-play-device-wrapper:last-child{margin-bottom:2px}.bx-remote-play-device-info{flex:1;padding:4px 0}.bx-remote-play-device-name{font-size:20px;font-weight:bold;display:inline-block;vertical-align:middle}.bx-remote-play-console-type{font-size:12px;background:#004c87;color:#fff;display:inline-block;border-radius:14px;padding:2px 10px;margin-left:8px;vertical-align:middle}.bx-remote-play-power-state{color:#888;font-size:12px}.bx-remote-play-connect-button{min-height:100%;margin:4px 0}.bx-remote-play-buttons{display:flex;justify-content:space-between}.bx-select{display:flex;align-items:center;flex:0 1 auto}.bx-select select{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-select > div,.bx-select button.bx-select-value{min-width:120px;text-align:left;margin:0 8px;line-height:24px;vertical-align:middle;background:#fff;color:#000;border-radius:4px;padding:2px 8px;flex:1}.bx-select > div{display:inline-block}.bx-select > div input{display:inline-block;margin-right:8px}.bx-select > div label{margin-bottom:0;font-size:14px;width:100%}.bx-select > div label span{display:block;font-size:10px;font-weight:bold;text-align:left;line-height:initial}.bx-select button.bx-select-value{border:none;display:inline-flex;cursor:pointer;min-height:30px;font-size:.9rem;align-items:center}.bx-select button.bx-select-value span{flex:1;text-align:left;display:inline-block}.bx-select button.bx-select-value input{margin:0 4px;accent-color:var(--bx-primary-button-color);pointer-events:none}.bx-select button.bx-select-value:hover input,.bx-select button.bx-select-value:focus input{accent-color:var(--bx-danger-button-color)}.bx-select button.bx-select-value:hover::after,.bx-select button.bx-select-value:focus::after{border-color:#4d4d4d !important}.bx-select button.bx-button{border:none;height:24px;width:24px;padding:0;line-height:24px;color:#fff;border-radius:4px;font-weight:bold;font-size:12px;font-family:var(--bx-monospaced-font);flex-shrink:0}.bx-select button.bx-button span{line-height:unset}.bx-guide-home-achievements-progress{display:flex;gap:10px;flex-direction:row}.bx-guide-home-achievements-progress .bx-button{margin-bottom:0 !important}html[data-xds-platform=tv] .bx-guide-home-achievements-progress{flex-direction:column}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress{flex-direction:row}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:first-of-type{flex:1}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:last-of-type{width:40px}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:last-of-type span{display:none}.bx-guide-home-buttons > div{display:flex;flex-direction:row;gap:12px}html[data-xds-platform=tv] .bx-guide-home-buttons > div{flex-direction:column}html[data-xds-platform=tv] .bx-guide-home-buttons > div button{margin-bottom:0 !important}html:not([data-xds-platform=tv]) .bx-guide-home-buttons > div button span{display:none}.bx-guide-home-buttons[data-is-playing="true"] button[data-state='normal']{display:none}.bx-guide-home-buttons[data-is-playing="false"] button[data-state='playing']{display:none}div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]{overflow:visible}.bx-stream-menu-button-on{fill:#000 !important;background-color:#2d2d2d !important;color:#000 !important}.bx-stream-refresh-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important}body[data-media-type=default] .bx-stream-refresh-button{left:calc(env(safe-area-inset-left, 0px) + 11px) !important}body[data-media-type=tv] .bx-stream-refresh-button{top:calc(var(--gds-focus-borderSize) + 80px) !important}.bx-stream-home-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px * 2) !important}body[data-media-type=default] .bx-stream-home-button{left:calc(env(safe-area-inset-left, 0px) + 12px) !important}body[data-media-type=tv] .bx-stream-home-button{top:calc(var(--gds-focus-borderSize) + 80px * 2) !important}div[data-testid=media-container]{display:flex}div[data-testid=media-container].bx-taking-screenshot:before{animation:bx-anim-taking-screenshot .5s ease;content:' ';position:absolute;width:100%;height:100%;z-index:var(--bx-screenshot-animation-z-index)}#game-stream video{margin:auto;align-self:center;background:#000}#game-stream canvas{position:absolute;align-self:center;margin:auto;left:0;right:0}#gamepass-dialog-root div[class^=Guide-module__guide] .bx-button{overflow:visible;margin-bottom:12px}@-moz-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-webkit-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-o-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}.bx-number-stepper{text-align:center}.bx-number-stepper span{display:inline-block;min-width:40px;font-family:var(--bx-monospaced-font);font-size:13px;margin:0 4px}.bx-number-stepper button{border:none;width:24px;height:24px;margin:0;line-height:24px;background-color:var(--bx-default-button-color);color:#fff;border-radius:4px;font-weight:bold;font-size:14px;font-family:var(--bx-monospaced-font)}@media (hover:hover){.bx-number-stepper button:hover{background-color:var(--bx-default-button-hover-color)}}.bx-number-stepper button:active{background-color:var(--bx-default-button-hover-color)}.bx-number-stepper button:disabled + span{font-family:var(--bx-title-font)}.bx-number-stepper input[type="range"]{display:block;margin:12px auto 2px;width:180px;color:#959595 !important}.bx-number-stepper input[type=range]:disabled,.bx-number-stepper button:disabled{display:none}.bx-number-stepper[data-disabled=true] input[type=range],.bx-number-stepper[data-disabled=true] button{display:none}#bx-game-bar{z-index:var(--bx-game-bar-z-index);position:fixed;bottom:0;width:40px;height:90px;overflow:visible;cursor:pointer}#bx-game-bar > svg{display:none;pointer-events:none;position:absolute;height:28px;margin-top:16px}@media (hover:hover){#bx-game-bar:hover > svg{display:block}}#bx-game-bar .bx-game-bar-container{opacity:0;position:absolute;display:flex;overflow:hidden;background:rgba(26,27,30,0.91);box-shadow:0 0 6px #1c1c1c;transition:opacity .1s ease-in}#bx-game-bar .bx-game-bar-container.bx-show{opacity:.9}#bx-game-bar .bx-game-bar-container.bx-show + svg{display:none !important}#bx-game-bar .bx-game-bar-container.bx-hide{opacity:0;pointer-events:none}#bx-game-bar .bx-game-bar-container button{width:60px;height:60px;border-radius:0}#bx-game-bar .bx-game-bar-container button svg{width:28px;height:28px;transition:transform .08s ease 0s}#bx-game-bar .bx-game-bar-container button:hover{border-radius:0}#bx-game-bar .bx-game-bar-container button:active svg{transform:scale(.75)}#bx-game-bar .bx-game-bar-container button.bx-activated{background-color:#fff}#bx-game-bar .bx-game-bar-container button.bx-activated svg{filter:invert(1)}#bx-game-bar .bx-game-bar-container div[data-activated] button{display:none}#bx-game-bar .bx-game-bar-container div[data-activated='false'] button:first-of-type{display:block}#bx-game-bar .bx-game-bar-container div[data-activated='true'] button:last-of-type{display:block}#bx-game-bar[data-position="bottom-left"]{left:0;direction:ltr}#bx-game-bar[data-position="bottom-left"] .bx-game-bar-container{border-radius:0 10px 10px 0}#bx-game-bar[data-position="bottom-right"]{right:0;direction:rtl}#bx-game-bar[data-position="bottom-right"] .bx-game-bar-container{direction:ltr;border-radius:10px 0 0 10px}.bx-badges{margin-left:0;user-select:none;-webkit-user-select:none}.bx-badge{border:none;display:inline-block;line-height:24px;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;font-weight:400;margin:0 8px 8px 0;box-shadow:0 0 6px #000;border-radius:4px}.bx-badge-name{background-color:#2d3036;border-radius:4px 0 0 4px}.bx-badge-name svg{width:16px;height:16px}.bx-badge-value{background-color:#808080;border-radius:0 4px 4px 0}.bx-badge-name,.bx-badge-value{display:inline-block;padding:0 8px;line-height:30px;vertical-align:bottom}.bx-badge-battery[data-charging=true] span:first-of-type::after{content:' ⚡️'}div[class^=StreamMenu-module__container] .bx-badges{position:absolute;max-width:500px}#gamepass-dialog-root .bx-badges{position:fixed;top:60px;left:460px;max-width:500px}@media (min-width:568px) and (max-height:480px){#gamepass-dialog-root .bx-badges{position:unset;top:unset;left:unset;margin:8px 0}}.bx-stats-bar{display:flex;flex-direction:row;gap:8px;user-select:none;-webkit-user-select:none;position:fixed;top:0;background-color:#000;color:#fff;font-family:var(--bx-monospaced-font);font-size:.9rem;padding-left:8px;z-index:var(--bx-stats-bar-z-index);text-wrap:nowrap}.bx-stats-bar[data-stats*="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats*="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats*="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats*="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats*="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats*="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats*="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats*="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats*="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats*="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats*="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats*="[ul]"] > .bx-stat-ul{display:inline-flex;align-items:baseline}.bx-stats-bar[data-stats$="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats$="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats$="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats$="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats$="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats$="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats$="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats$="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats$="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats$="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats$="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats$="[ul]"] > .bx-stat-ul{border-right:none}.bx-stats-bar::before{display:none;content:'👀';vertical-align:middle;margin-right:8px}.bx-stats-bar[data-display=glancing]::before{display:inline-block}.bx-stats-bar[data-position=top-left]{left:0;border-radius:0 0 4px 0}.bx-stats-bar[data-position=top-right]{right:0;border-radius:0 0 0 4px}.bx-stats-bar[data-position=top-center]{transform:translate(-50%,0);left:50%;border-radius:0 0 4px 4px}.bx-stats-bar[data-transparent=true]{background:none;filter:drop-shadow(1px 0 0 rgba(0,0,0,0.941)) drop-shadow(-1px 0 0 rgba(0,0,0,0.941)) drop-shadow(0 1px 0 rgba(0,0,0,0.941)) drop-shadow(0 -1px 0 rgba(0,0,0,0.941))}.bx-stats-bar > div{display:none;border-right:1px solid #fff;padding-right:8px}.bx-stats-bar label{margin:0 8px 0 0;font-family:var(--bx-title-font);font-size:70%;font-weight:bold;vertical-align:middle;cursor:help}.bx-stats-bar span{min-width:60px;display:inline-block;text-align:right;vertical-align:middle}.bx-stats-bar span[data-grade=good]{color:#6bffff}.bx-stats-bar span[data-grade=ok]{color:#fff16b}.bx-stats-bar span[data-grade=bad]{color:#ff5f5f}.bx-stats-bar span:first-of-type{min-width:22px}.bx-mkb-settings{display:flex;flex-direction:column;flex:1;padding-bottom:10px;overflow:hidden}.bx-mkb-settings select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;text-align:right;border:none;color:#fff}.bx-mkb-pointer-lock-msg{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:50%;transform:translateX(-50%) translateY(-50%);margin:auto;background:#151515;z-index:var(--bx-mkb-pointer-lock-msg-z-index);color:#fff;text-align:center;font-weight:400;font-family:"Segoe UI",Arial,Helvetica,sans-serif;font-size:1.3rem;padding:12px;border-radius:8px;align-items:center;box-shadow:0 0 6px #000;min-width:220px;opacity:.9}.bx-mkb-pointer-lock-msg:hover{opacity:1}.bx-mkb-pointer-lock-msg > div:first-of-type{display:flex;flex-direction:column;text-align:left}.bx-mkb-pointer-lock-msg p{margin:0}.bx-mkb-pointer-lock-msg p:first-child{font-size:22px;margin-bottom:4px;font-weight:bold}.bx-mkb-pointer-lock-msg p:last-child{font-size:12px;font-style:italic}.bx-mkb-pointer-lock-msg > div:last-of-type{margin-top:10px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='native'] button:first-of-type{margin-bottom:8px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div{display:flex;flex-flow:row;margin-top:8px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button{flex:1}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button:first-of-type{margin-right:5px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button:last-of-type{margin-left:5px}.bx-mkb-preset-tools{display:flex;margin-bottom:12px}.bx-mkb-preset-tools select{flex:1}.bx-mkb-preset-tools button{margin-left:6px}.bx-mkb-settings-rows{flex:1;overflow:scroll}.bx-mkb-key-row{display:flex;margin-bottom:10px;align-items:center}.bx-mkb-key-row label{margin-bottom:0;font-family:var(--bx-promptfont-font);font-size:26px;text-align:center;width:26px;height:32px;line-height:32px}.bx-mkb-key-row button{flex:1;height:32px;line-height:32px;margin:0 0 0 10px;background:transparent;border:none;color:#fff;border-radius:0;border-left:1px solid #373737}.bx-mkb-key-row button:hover{background:transparent;cursor:default}.bx-mkb-settings.bx-editing .bx-mkb-key-row button{background:#393939;border-radius:4px;border:none}.bx-mkb-settings.bx-editing .bx-mkb-key-row button:hover{background:#333;cursor:pointer}.bx-mkb-action-buttons > div{text-align:right;display:none}.bx-mkb-action-buttons button{margin-left:8px}.bx-mkb-settings:not(.bx-editing) .bx-mkb-action-buttons > div:first-child{display:block}.bx-mkb-settings.bx-editing .bx-mkb-action-buttons > div:last-child{display:block}.bx-mkb-note{display:block;margin:16px 0 10px;font-size:12px}.bx-mkb-note:first-of-type{margin-top:0}.bx-product-details-buttons{display:flex;gap:10px;flex-direction:row}.bx-product-details-buttons button{max-width:max-content;margin:10px 0 0 0;display:flex}@media (min-width:568px) and (max-height:480px){.bx-product-details-buttons{flex-direction:column}.bx-product-details-buttons button{margin:8px 0 0 10px}}`; - const PREF_HIDE_SECTIONS = getPref("ui_hide_sections"), selectorToHide = []; + let css = `:root{--bx-title-font:Bahnschrift,Arial,Helvetica,sans-serif;--bx-title-font-semibold:Bahnschrift Semibold,Arial,Helvetica,sans-serif;--bx-normal-font:"Segoe UI",Arial,Helvetica,sans-serif;--bx-monospaced-font:Consolas,"Courier New",Courier,monospace;--bx-promptfont-font:promptfont;--bx-button-height:40px;--bx-default-button-color:#2d3036;--bx-default-button-rgb:45,48,54;--bx-default-button-hover-color:#515863;--bx-default-button-hover-rgb:81,88,99;--bx-default-button-active-color:#222428;--bx-default-button-active-rgb:34,36,40;--bx-default-button-disabled-color:#8e8e8e;--bx-default-button-disabled-rgb:142,142,142;--bx-primary-button-color:#008746;--bx-primary-button-rgb:0,135,70;--bx-primary-button-hover-color:#04b358;--bx-primary-button-hover-rgb:4,179,88;--bx-primary-button-active-color:#044e2a;--bx-primary-button-active-rgb:4,78,42;--bx-primary-button-disabled-color:#448262;--bx-primary-button-disabled-rgb:68,130,98;--bx-danger-button-color:#c10404;--bx-danger-button-rgb:193,4,4;--bx-danger-button-hover-color:#e61d1d;--bx-danger-button-hover-rgb:230,29,29;--bx-danger-button-active-color:#a26c6c;--bx-danger-button-active-rgb:162,108,108;--bx-danger-button-disabled-color:#df5656;--bx-danger-button-disabled-rgb:223,86,86;--bx-fullscreen-text-z-index:99999;--bx-toast-z-index:60000;--bx-dialog-z-index:50000;--bx-dialog-overlay-z-index:40200;--bx-stats-bar-z-index:40100;--bx-mkb-pointer-lock-msg-z-index:40000;--bx-navigation-dialog-z-index:30100;--bx-navigation-dialog-overlay-z-index:30000;--bx-game-bar-z-index:10000;--bx-screenshot-animation-z-index:9000;--bx-wait-time-box-z-index:1000}@font-face{font-family:'promptfont';src:url("https://redphx.github.io/better-xcloud/fonts/promptfont.otf")}div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module__hiddenContainer]){opacity:0;pointer-events:none !important;position:absolute;top:-9999px;left:-9999px}@media screen and (max-width:640px){header a[href="/play"]{display:none}}.bx-full-width{width:100% !important}.bx-full-height{height:100% !important}.bx-no-scroll{overflow:hidden !important}.bx-hide-scroll-bar{scrollbar-width:none}.bx-hide-scroll-bar::-webkit-scrollbar{display:none}.bx-gone{display:none !important}.bx-offscreen{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-hidden{visibility:hidden !important}.bx-invisible{opacity:0}.bx-unclickable{pointer-events:none}.bx-pixel{width:1px !important;height:1px !important}.bx-no-margin{margin:0 !important}.bx-no-padding{padding:0 !important}.bx-prompt{font-family:var(--bx-promptfont-font)}.bx-line-through{text-decoration:line-through !important}.bx-normal-case{text-transform:none !important}.bx-normal-link{text-transform:none !important;text-align:left !important;font-weight:400 !important;font-family:var(--bx-normal-font) !important}select[multiple]{overflow:auto}#headerArea,#uhfSkipToMain,.uhf-footer{display:none}div[class*=NotFocusedDialog]{position:absolute !important;top:-9999px !important;left:-9999px !important;width:0 !important;height:0 !important}#game-stream video:not([src]){visibility:hidden}div[class*=SupportedInputsBadge]:not(:has(:nth-child(2))),div[class*=SupportedInputsBadge] svg:first-of-type{display:none}.bx-game-tile-wait-time{position:absolute;top:0;left:0;z-index:1;background:rgba(0,0,0,0.549);display:flex;border-radius:4px 0 4px 0;align-items:center;padding:4px 8px}.bx-game-tile-wait-time svg{width:14px;height:16px;margin-right:2px}.bx-game-tile-wait-time span{display:inline-block;height:16px;line-height:16px;font-size:12px;font-weight:bold;margin-left:2px}.bx-fullscreen-text{position:fixed;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,0.8);z-index:var(--bx-fullscreen-text-z-index);line-height:100vh;color:#fff;text-align:center;font-weight:400;font-family:var(--bx-normal-font);font-size:1.3rem;user-select:none;-webkit-user-select:none}#root section[class*=DeviceCodePage-module__page]{margin-left:20px !important;margin-right:20px !important;margin-top:20px !important;max-width:800px !important}#root div[class*=DeviceCodePage-module__back]{display:none}.bx-button{--button-rgb:var(--bx-default-button-rgb);--button-hover-rgb:var(--bx-default-button-hover-rgb);--button-active-rgb:var(--bx-default-button-active-rgb);--button-disabled-rgb:var(--bx-default-button-disabled-rgb);background-color:rgb(var(--button-rgb));user-select:none;-webkit-user-select:none;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;border:none;font-weight:400;height:var(--bx-button-height);border-radius:4px;padding:0 8px;text-transform:uppercase;cursor:pointer;overflow:hidden}.bx-button:not([disabled]):active{background-color:rgb(var(--button-active-rgb))}.bx-button:focus{outline:none !important}.bx-button:not([disabled]):not(:active):hover,.bx-button:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button:disabled{cursor:default;background-color:rgb(var(--button-disabled-rgb))}.bx-button.bx-ghost{background-color:transparent}.bx-button.bx-ghost:not([disabled]):not(:active):hover,.bx-button.bx-ghost:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button.bx-primary{--button-rgb:var(--bx-primary-button-rgb)}.bx-button.bx-primary:not([disabled]):active{--button-active-rgb:var(--bx-primary-button-active-rgb)}.bx-button.bx-primary:not([disabled]):not(:active):hover,.bx-button.bx-primary:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-primary-button-hover-rgb)}.bx-button.bx-primary:disabled{--button-disabled-rgb:var(--bx-primary-button-disabled-rgb)}.bx-button.bx-danger{--button-rgb:var(--bx-danger-button-rgb)}.bx-button.bx-danger:not([disabled]):active{--button-active-rgb:var(--bx-danger-button-active-rgb)}.bx-button.bx-danger:not([disabled]):not(:active):hover,.bx-button.bx-danger:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-danger-button-hover-rgb)}.bx-button.bx-danger:disabled{--button-disabled-rgb:var(--bx-danger-button-disabled-rgb)}.bx-button.bx-frosted{--button-alpha:.2;background-color:rgba(var(--button-rgb), var(--button-alpha));backdrop-filter:blur(4px) brightness(1.5)}.bx-button.bx-frosted:not([disabled]):not(:active):hover,.bx-button.bx-frosted:not([disabled]):not(:active).bx-focusable:focus{background-color:rgba(var(--button-hover-rgb), var(--button-alpha))}.bx-button.bx-drop-shadow{box-shadow:0 0 4px rgba(0,0,0,0.502)}.bx-button.bx-tall{height:calc(var(--bx-button-height) * 1.5) !important}.bx-button.bx-circular{border-radius:var(--bx-button-height);height:var(--bx-button-height)}.bx-button svg{display:inline-block;width:16px;height:var(--bx-button-height)}.bx-button span{display:inline-block;line-height:var(--bx-button-height);vertical-align:middle;color:#fff;overflow:hidden;white-space:nowrap}.bx-button span:not(:only-child){margin-left:10px}.bx-focusable{position:relative;overflow:visible}.bx-focusable::after{border:2px solid transparent;border-radius:10px}.bx-focusable:focus::after{content:'';border-color:#fff;position:absolute;top:-6px;left:-6px;right:-6px;bottom:-6px}html[data-active-input=touch] .bx-focusable:focus::after,html[data-active-input=mouse] .bx-focusable:focus::after{border-color:transparent !important}.bx-focusable.bx-circular::after{border-radius:var(--bx-button-height)}a.bx-button{display:inline-block}a.bx-button.bx-full-width{text-align:center}button.bx-inactive{pointer-events:none;opacity:.2;background:transparent !important}.bx-header-remote-play-button{height:auto;margin-right:8px !important}.bx-header-remote-play-button svg{width:24px;height:24px}.bx-header-settings-button{line-height:30px;font-size:14px;text-transform:uppercase;position:relative}.bx-header-settings-button[data-update-available]::before{content:'🌟' !important;line-height:var(--bx-button-height);display:inline-block;margin-left:4px}.bx-dialog-overlay{position:fixed;inset:0;z-index:var(--bx-dialog-overlay-z-index);background:#000;opacity:50%}.bx-dialog{display:flex;flex-flow:column;max-height:90vh;position:fixed;top:50%;left:50%;margin-right:-50%;transform:translate(-50%,-50%);min-width:420px;padding:20px;border-radius:8px;z-index:var(--bx-dialog-z-index);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-normal-font);box-shadow:0 0 6px #000;user-select:none;-webkit-user-select:none}.bx-dialog *:focus{outline:none !important}.bx-dialog h2{display:flex;margin-bottom:12px}.bx-dialog h2 b{flex:1;color:#fff;display:block;font-family:var(--bx-title-font);font-size:26px;font-weight:400;line-height:var(--bx-button-height)}.bx-dialog.bx-binding-dialog h2 b{font-family:var(--bx-promptfont-font) !important}.bx-dialog > div{overflow:auto;padding:2px 0}.bx-dialog > button{padding:8px 32px;margin:10px auto 0;border:none;border-radius:4px;display:block;background-color:#2d3036;text-align:center;color:#fff;text-transform:uppercase;font-family:var(--bx-title-font);font-weight:400;line-height:18px;font-size:14px}@media (hover:hover){.bx-dialog > button:hover{background-color:#515863}}.bx-dialog > button:focus{background-color:#515863}@media screen and (max-width:450px){.bx-dialog{min-width:100%}}.bx-navigation-dialog{position:absolute;z-index:var(--bx-navigation-dialog-z-index);font-family:var(--bx-title-font)}.bx-navigation-dialog *:focus{outline:none !important}.bx-navigation-dialog-overlay{position:fixed;background:rgba(11,11,11,0.89);top:0;left:0;right:0;bottom:0;z-index:var(--bx-navigation-dialog-overlay-z-index)}.bx-navigation-dialog-overlay[data-is-playing="true"]{background:transparent}.bx-settings-dialog{display:flex;position:fixed;top:0;right:0;bottom:0;opacity:.98;user-select:none;-webkit-user-select:none}.bx-settings-dialog .bx-focusable::after{border-radius:4px}.bx-settings-dialog .bx-focusable:focus::after{top:0;left:0;right:0;bottom:0}.bx-settings-dialog .bx-settings-reload-note{font-size:.8rem;display:block;padding:8px;font-style:italic;font-weight:normal;height:var(--bx-button-height)}.bx-settings-dialog input{accent-color:var(--bx-primary-button-color)}.bx-settings-dialog input:focus{accent-color:var(--bx-danger-button-color)}.bx-settings-dialog select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;border:none;color:#fff}.bx-settings-dialog select option:disabled{display:none}.bx-settings-dialog input[type=checkbox]:focus,.bx-settings-dialog select:focus{filter:drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff)}.bx-settings-dialog a{color:#1c9d1c;text-decoration:none}.bx-settings-dialog a:hover,.bx-settings-dialog a:focus{color:#5dc21e}.bx-settings-tabs-container{position:fixed;width:48px;max-height:100vh;display:flex;flex-direction:column}.bx-settings-tabs-container > div:last-of-type{display:flex;flex-direction:column;align-items:end}.bx-settings-tabs-container > div:last-of-type button{flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0;margin-top:8px;height:unset;padding:8px 10px}.bx-settings-tabs-container > div:last-of-type button svg{width:16px;height:16px}.bx-settings-tabs{display:flex;flex-direction:column;border-radius:0 0 0 8px;box-shadow:0 0 6px #000;overflow:overlay;flex:1}.bx-settings-tabs svg{width:24px;height:24px;padding:10px;flex-shrink:0;box-sizing:content-box;background:#131313;cursor:pointer;border-left:4px solid #1e1e1e}.bx-settings-tabs svg.bx-active{background:#222;border-color:#008746}.bx-settings-tabs svg:not(.bx-active):hover{background:#2f2f2f;border-color:#484848}.bx-settings-tabs svg:focus{border-color:#fff}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]{background:var(--bx-danger-button-color) !important}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]:hover{background:var(--bx-danger-button-hover-color) !important}.bx-settings-tab-contents{flex-direction:column;padding:10px;margin-left:48px;width:450px;max-width:calc(100vw - tabsWidth);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-title-font);text-align:center;box-shadow:0 0 6px #000;overflow:overlay;z-index:1}.bx-settings-tab-contents > div[data-tab-group=mkb]{display:flex;flex-direction:column;height:100%;overflow:hidden}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=true] > div:first-of-type{display:none}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=true] > div:last-of-type{display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=false] > div:first-of-type{display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=false] > div:last-of-type{display:none}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-profile{width:100%;height:36px;display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-note{margin-top:10px;font-size:14px}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row{display:flex;margin-bottom:10px}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row label.bx-prompt{flex:1;font-size:26px;margin-bottom:0}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions{flex:2;position:relative}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions select{position:absolute;width:100%;height:100%;display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions select:last-of-type{opacity:0;z-index:calc(var(--bx-settings-z-index) + 1)}.bx-settings-tab-contents .bx-top-buttons{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}.bx-settings-tab-contents .bx-top-buttons .bx-button{display:block}.bx-settings-tab-contents h2{margin:16px 0 8px 0;display:flex;align-items:center}.bx-settings-tab-contents h2:first-of-type{margin-top:0}.bx-settings-tab-contents h2 span{display:inline-block;font-size:20px;font-weight:bold;text-align:left;flex:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}@media (max-width:500px){.bx-settings-tab-contents{width:calc(100vw - 48px)}}.bx-settings-row{display:flex;gap:10px;padding:16px 10px;margin:0;background:#2a2a2a;border-bottom:1px solid #343434}.bx-settings-row:hover,.bx-settings-row:focus-within{background-color:#242424}.bx-settings-row:not(:has(> input[type=checkbox])){flex-wrap:wrap}.bx-settings-row > span.bx-settings-label{font-size:14px;display:block;text-align:left;align-self:center;margin-bottom:0 !important;flex:1}.bx-settings-row > span.bx-settings-label + *{margin:0 0 0 auto}.bx-settings-dialog-note{display:block;color:#afafb0;font-size:12px;font-weight:lighter;font-style:italic}.bx-settings-dialog-note:not(:has(a)){margin-top:4px}.bx-settings-dialog-note a{display:inline-block;padding:4px}.bx-settings-custom-user-agent{display:block;width:100%;padding:6px}.bx-donation-link{display:block;text-align:center;text-decoration:none;height:20px;line-height:20px;font-size:14px;margin-top:10px}.bx-debug-info button{margin-top:10px}.bx-debug-info pre{margin-top:10px;cursor:copy;color:#fff;padding:8px;border:1px solid #2d2d2d;background:#212121;white-space:break-spaces;text-align:left}.bx-debug-info pre:hover{background:#272727}.bx-settings-app-version{margin-top:10px;text-align:center;color:#747474;font-size:12px}.bx-note-unsupported{display:block;font-size:12px;font-style:italic;font-weight:normal;color:#828282}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:has(+ .bx-settings-row){border-top-left-radius:10px;border-top-right-radius:10px}.bx-settings-tab-contents > div .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-bottom-left-radius:10px;border-bottom-right-radius:10px}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-radius:10px}.bx-suggest-toggler{text-align:left;display:flex;border-radius:4px;overflow:hidden;background:#003861}.bx-suggest-toggler label{flex:1;margin-bottom:0;padding:10px;background:#004f87}.bx-suggest-toggler span{display:inline-block;align-self:center;padding:10px;width:40px;text-align:center}.bx-suggest-toggler:hover,.bx-suggest-toggler:focus{cursor:pointer;background:#005da1}.bx-suggest-toggler:hover label,.bx-suggest-toggler:focus label{background:#006fbe}.bx-suggest-toggler[bx-open] span{transform:rotate(90deg)}.bx-suggest-toggler[bx-open]+ .bx-suggest-box{display:block}.bx-suggest-box{display:none;background:#161616;padding:10px;box-shadow:0 0 12px #0f0f0f inset;border-radius:10px}.bx-suggest-wrapper{display:flex;flex-direction:column;gap:10px;margin:10px}.bx-suggest-note{font-size:11px;color:#8c8c8c;font-style:italic;font-weight:100}.bx-suggest-link{font-size:14px;display:inline-block;margin-top:4px;padding:4px}.bx-suggest-row{display:flex;flex-direction:row;gap:10px}.bx-suggest-row label{flex:1;overflow:overlay;border-radius:4px}.bx-suggest-row label .bx-suggest-label{background:#323232;padding:4px 10px;font-size:12px;text-align:left}.bx-suggest-row label .bx-suggest-value{padding:6px;font-size:14px}.bx-suggest-row label .bx-suggest-value.bx-suggest-change{background-color:var(--bx-warning-color)}.bx-suggest-row.bx-suggest-ok input{visibility:hidden}.bx-suggest-row.bx-suggest-ok .bx-suggest-label{background-color:#008114}.bx-suggest-row.bx-suggest-ok .bx-suggest-value{background-color:#13a72a}.bx-suggest-row.bx-suggest-change .bx-suggest-label{background-color:#a65e08}.bx-suggest-row.bx-suggest-change .bx-suggest-value{background-color:#d57f18}.bx-suggest-row.bx-suggest-change:hover label{cursor:pointer}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-label{background-color:#995707}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-value{background-color:#bd7115}.bx-suggest-row.bx-suggest-change input:not(:checked) + label{opacity:.5}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-label{background-color:#2a2a2a}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-value{background-color:#393939}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label{opacity:1}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-label{background-color:#202020}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-value{background-color:#303030}.bx-toast{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:24px;transform:translate(-50%,0);background:#000;border-radius:16px;color:#fff;z-index:var(--bx-toast-z-index);font-family:var(--bx-normal-font);border:2px solid #fff;display:flex;align-items:center;opacity:0;overflow:clip;transition:opacity .2s ease-in}.bx-toast.bx-show{opacity:.85}.bx-toast.bx-hide{opacity:0;pointer-events:none}.bx-toast-msg{font-size:14px;display:inline-block;padding:12px 16px;white-space:pre}.bx-toast-status{font-weight:bold;font-size:14px;text-transform:uppercase;display:inline-block;background:#515863;padding:12px 16px;color:#fff;white-space:pre}.bx-wait-time-box{position:fixed;top:0;right:0;background-color:rgba(0,0,0,0.8);color:#fff;z-index:var(--bx-wait-time-box-z-index);padding:12px;border-radius:0 0 0 8px}.bx-wait-time-box label{display:block;text-transform:uppercase;text-align:right;font-size:12px;font-weight:bold;margin:0}.bx-wait-time-box span{display:block;font-family:var(--bx-monospaced-font);text-align:right;font-size:16px;margin-bottom:10px}.bx-wait-time-box span:last-of-type{margin-bottom:0}.bx-remote-play-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;width:420px;max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:20px}.bx-remote-play-container > .bx-button{display:table;margin:0 0 0 auto}.bx-remote-play-settings{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #2d2d2d}.bx-remote-play-settings > div{display:flex}.bx-remote-play-settings label{flex:1}.bx-remote-play-settings label p{margin:4px 0 0;padding:0;color:#888;font-size:12px}.bx-remote-play-resolution{display:block}.bx-remote-play-resolution input[type="radio"]{accent-color:var(--bx-primary-button-color);margin-right:6px}.bx-remote-play-resolution input[type="radio"]:focus{accent-color:var(--bx-primary-button-hover-color)}.bx-remote-play-device-wrapper{display:flex;margin-bottom:12px}.bx-remote-play-device-wrapper:last-child{margin-bottom:2px}.bx-remote-play-device-info{flex:1;padding:4px 0}.bx-remote-play-device-name{font-size:20px;font-weight:bold;display:inline-block;vertical-align:middle}.bx-remote-play-console-type{font-size:12px;background:#004c87;color:#fff;display:inline-block;border-radius:14px;padding:2px 10px;margin-left:8px;vertical-align:middle}.bx-remote-play-power-state{color:#888;font-size:12px}.bx-remote-play-connect-button{min-height:100%;margin:4px 0}.bx-remote-play-buttons{display:flex;justify-content:space-between}.bx-select{display:flex;align-items:center;flex:0 1 auto}.bx-select select{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-select > div,.bx-select button.bx-select-value{min-width:120px;text-align:left;margin:0 8px;line-height:24px;vertical-align:middle;background:#fff;color:#000;border-radius:4px;padding:2px 8px;flex:1}.bx-select > div{display:inline-block}.bx-select > div input{display:inline-block;margin-right:8px}.bx-select > div label{margin-bottom:0;font-size:14px;width:100%}.bx-select > div label span{display:block;font-size:10px;font-weight:bold;text-align:left;line-height:initial}.bx-select button.bx-select-value{border:none;display:inline-flex;cursor:pointer;min-height:30px;font-size:.9rem;align-items:center}.bx-select button.bx-select-value span{flex:1;text-align:left;display:inline-block}.bx-select button.bx-select-value input{margin:0 4px;accent-color:var(--bx-primary-button-color);pointer-events:none}.bx-select button.bx-select-value:hover input,.bx-select button.bx-select-value:focus input{accent-color:var(--bx-danger-button-color)}.bx-select button.bx-select-value:hover::after,.bx-select button.bx-select-value:focus::after{border-color:#4d4d4d !important}.bx-select button.bx-button{border:none;height:24px;width:24px;padding:0;line-height:24px;color:#fff;border-radius:4px;font-weight:bold;font-size:12px;font-family:var(--bx-monospaced-font);flex-shrink:0}.bx-select button.bx-button span{line-height:unset}.bx-guide-home-achievements-progress{display:flex;gap:10px;flex-direction:row}.bx-guide-home-achievements-progress .bx-button{margin-bottom:0 !important}html[data-xds-platform=tv] .bx-guide-home-achievements-progress{flex-direction:column}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress{flex-direction:row}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:first-of-type{flex:1}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:last-of-type{width:40px}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:last-of-type span{display:none}.bx-guide-home-buttons > div{display:flex;flex-direction:row;gap:12px}html[data-xds-platform=tv] .bx-guide-home-buttons > div{flex-direction:column}html[data-xds-platform=tv] .bx-guide-home-buttons > div button{margin-bottom:0 !important}html:not([data-xds-platform=tv]) .bx-guide-home-buttons > div button span{display:none}.bx-guide-home-buttons[data-is-playing="true"] button[data-state='normal']{display:none}.bx-guide-home-buttons[data-is-playing="false"] button[data-state='playing']{display:none}div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]{overflow:visible}.bx-stream-menu-button-on{fill:#000 !important;background-color:#2d2d2d !important;color:#000 !important}.bx-stream-refresh-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important}body[data-media-type=default] .bx-stream-refresh-button{left:calc(env(safe-area-inset-left, 0px) + 11px) !important}body[data-media-type=tv] .bx-stream-refresh-button{top:calc(var(--gds-focus-borderSize) + 80px) !important}.bx-stream-home-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px * 2) !important}body[data-media-type=default] .bx-stream-home-button{left:calc(env(safe-area-inset-left, 0px) + 12px) !important}body[data-media-type=tv] .bx-stream-home-button{top:calc(var(--gds-focus-borderSize) + 80px * 2) !important}div[data-testid=media-container]{display:flex}div[data-testid=media-container].bx-taking-screenshot:before{animation:bx-anim-taking-screenshot .5s ease;content:' ';position:absolute;width:100%;height:100%;z-index:var(--bx-screenshot-animation-z-index)}#game-stream video{margin:auto;align-self:center;background:#000}#game-stream canvas{position:absolute;align-self:center;margin:auto;left:0;right:0}#gamepass-dialog-root div[class^=Guide-module__guide] .bx-button{overflow:visible;margin-bottom:12px}@-moz-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-webkit-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-o-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}.bx-number-stepper{text-align:center}.bx-number-stepper span{display:inline-block;min-width:40px;font-family:var(--bx-monospaced-font);font-size:13px;margin:0 4px}.bx-number-stepper button{border:none;width:24px;height:24px;margin:0;line-height:24px;background-color:var(--bx-default-button-color);color:#fff;border-radius:4px;font-weight:bold;font-size:14px;font-family:var(--bx-monospaced-font)}@media (hover:hover){.bx-number-stepper button:hover{background-color:var(--bx-default-button-hover-color)}}.bx-number-stepper button:active{background-color:var(--bx-default-button-hover-color)}.bx-number-stepper button:disabled + span{font-family:var(--bx-title-font)}.bx-number-stepper input[type="range"]{display:block;margin:12px auto 2px;width:180px;color:#959595 !important}.bx-number-stepper input[type=range]:disabled,.bx-number-stepper button:disabled{display:none}.bx-number-stepper[data-disabled=true] input[type=range],.bx-number-stepper[data-disabled=true] button{display:none}#bx-game-bar{z-index:var(--bx-game-bar-z-index);position:fixed;bottom:0;width:40px;height:90px;overflow:visible;cursor:pointer}#bx-game-bar > svg{display:none;pointer-events:none;position:absolute;height:28px;margin-top:16px}@media (hover:hover){#bx-game-bar:hover > svg{display:block}}#bx-game-bar .bx-game-bar-container{opacity:0;position:absolute;display:flex;overflow:hidden;background:rgba(26,27,30,0.91);box-shadow:0 0 6px #1c1c1c;transition:opacity .1s ease-in}#bx-game-bar .bx-game-bar-container.bx-show{opacity:.9}#bx-game-bar .bx-game-bar-container.bx-show + svg{display:none !important}#bx-game-bar .bx-game-bar-container.bx-hide{opacity:0;pointer-events:none}#bx-game-bar .bx-game-bar-container button{width:60px;height:60px;border-radius:0}#bx-game-bar .bx-game-bar-container button svg{width:28px;height:28px;transition:transform .08s ease 0s}#bx-game-bar .bx-game-bar-container button:hover{border-radius:0}#bx-game-bar .bx-game-bar-container button:active svg{transform:scale(.75)}#bx-game-bar .bx-game-bar-container button.bx-activated{background-color:#fff}#bx-game-bar .bx-game-bar-container button.bx-activated svg{filter:invert(1)}#bx-game-bar .bx-game-bar-container div[data-activated] button{display:none}#bx-game-bar .bx-game-bar-container div[data-activated='false'] button:first-of-type{display:block}#bx-game-bar .bx-game-bar-container div[data-activated='true'] button:last-of-type{display:block}#bx-game-bar[data-position="bottom-left"]{left:0;direction:ltr}#bx-game-bar[data-position="bottom-left"] .bx-game-bar-container{border-radius:0 10px 10px 0}#bx-game-bar[data-position="bottom-right"]{right:0;direction:rtl}#bx-game-bar[data-position="bottom-right"] .bx-game-bar-container{direction:ltr;border-radius:10px 0 0 10px}.bx-badges{margin-left:0;user-select:none;-webkit-user-select:none}.bx-badge{border:none;display:inline-block;line-height:24px;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;font-weight:400;margin:0 8px 8px 0;box-shadow:0 0 6px #000;border-radius:4px}.bx-badge-name{background-color:#2d3036;border-radius:4px 0 0 4px}.bx-badge-name svg{width:16px;height:16px}.bx-badge-value{background-color:#808080;border-radius:0 4px 4px 0}.bx-badge-name,.bx-badge-value{display:inline-block;padding:0 8px;line-height:30px;vertical-align:bottom}.bx-badge-battery[data-charging=true] span:first-of-type::after{content:' ⚡️'}div[class^=StreamMenu-module__container] .bx-badges{position:absolute;max-width:500px}#gamepass-dialog-root .bx-badges{position:fixed;top:60px;left:460px;max-width:500px}@media (min-width:568px) and (max-height:480px){#gamepass-dialog-root .bx-badges{position:unset;top:unset;left:unset;margin:8px 0}}.bx-stats-bar{display:flex;flex-direction:row;gap:8px;user-select:none;-webkit-user-select:none;position:fixed;top:0;background-color:#000;color:#fff;font-family:var(--bx-monospaced-font);font-size:.9rem;padding-left:8px;z-index:var(--bx-stats-bar-z-index);text-wrap:nowrap}.bx-stats-bar[data-stats*="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats*="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats*="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats*="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats*="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats*="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats*="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats*="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats*="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats*="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats*="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats*="[ul]"] > .bx-stat-ul{display:inline-flex;align-items:baseline}.bx-stats-bar[data-stats$="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats$="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats$="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats$="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats$="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats$="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats$="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats$="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats$="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats$="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats$="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats$="[ul]"] > .bx-stat-ul{border-right:none}.bx-stats-bar::before{display:none;content:'👀';vertical-align:middle;margin-right:8px}.bx-stats-bar[data-display=glancing]::before{display:inline-block}.bx-stats-bar[data-position=top-left]{left:0;border-radius:0 0 4px 0}.bx-stats-bar[data-position=top-right]{right:0;border-radius:0 0 0 4px}.bx-stats-bar[data-position=top-center]{transform:translate(-50%,0);left:50%;border-radius:0 0 4px 4px}.bx-stats-bar[data-transparent=true]{background:none;filter:drop-shadow(1px 0 0 rgba(0,0,0,0.941)) drop-shadow(-1px 0 0 rgba(0,0,0,0.941)) drop-shadow(0 1px 0 rgba(0,0,0,0.941)) drop-shadow(0 -1px 0 rgba(0,0,0,0.941))}.bx-stats-bar > div{display:none;border-right:1px solid #fff;padding-right:8px}.bx-stats-bar label{margin:0 8px 0 0;font-family:var(--bx-title-font);font-size:70%;font-weight:bold;vertical-align:middle;cursor:help}.bx-stats-bar span{min-width:60px;display:inline-block;text-align:right;vertical-align:middle}.bx-stats-bar span[data-grade=good]{color:#6bffff}.bx-stats-bar span[data-grade=ok]{color:#fff16b}.bx-stats-bar span[data-grade=bad]{color:#ff5f5f}.bx-stats-bar span:first-of-type{min-width:22px}.bx-mkb-settings{display:flex;flex-direction:column;flex:1;padding-bottom:10px;overflow:hidden}.bx-mkb-settings select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;text-align:right;border:none;color:#fff}.bx-mkb-pointer-lock-msg{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:50%;transform:translateX(-50%) translateY(-50%);margin:auto;background:#151515;z-index:var(--bx-mkb-pointer-lock-msg-z-index);color:#fff;text-align:center;font-weight:400;font-family:"Segoe UI",Arial,Helvetica,sans-serif;font-size:1.3rem;padding:12px;border-radius:8px;align-items:center;box-shadow:0 0 6px #000;min-width:220px;opacity:.9}.bx-mkb-pointer-lock-msg:hover{opacity:1}.bx-mkb-pointer-lock-msg > div:first-of-type{display:flex;flex-direction:column;text-align:left}.bx-mkb-pointer-lock-msg p{margin:0}.bx-mkb-pointer-lock-msg p:first-child{font-size:22px;margin-bottom:4px;font-weight:bold}.bx-mkb-pointer-lock-msg p:last-child{font-size:12px;font-style:italic}.bx-mkb-pointer-lock-msg > div:last-of-type{margin-top:10px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='native'] button:first-of-type{margin-bottom:8px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div{display:flex;flex-flow:row;margin-top:8px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button{flex:1}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button:first-of-type{margin-right:5px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button:last-of-type{margin-left:5px}.bx-mkb-preset-tools{display:flex;margin-bottom:12px}.bx-mkb-preset-tools select{flex:1}.bx-mkb-preset-tools button{margin-left:6px}.bx-mkb-settings-rows{flex:1;overflow:scroll}.bx-mkb-key-row{display:flex;margin-bottom:10px;align-items:center}.bx-mkb-key-row label{margin-bottom:0;font-family:var(--bx-promptfont-font);font-size:26px;text-align:center;width:26px;height:32px;line-height:32px}.bx-mkb-key-row button{flex:1;height:32px;line-height:32px;margin:0 0 0 10px;background:transparent;border:none;color:#fff;border-radius:0;border-left:1px solid #373737}.bx-mkb-key-row button:hover{background:transparent;cursor:default}.bx-mkb-settings.bx-editing .bx-mkb-key-row button{background:#393939;border-radius:4px;border:none}.bx-mkb-settings.bx-editing .bx-mkb-key-row button:hover{background:#333;cursor:pointer}.bx-mkb-action-buttons > div{text-align:right;display:none}.bx-mkb-action-buttons button{margin-left:8px}.bx-mkb-settings:not(.bx-editing) .bx-mkb-action-buttons > div:first-child{display:block}.bx-mkb-settings.bx-editing .bx-mkb-action-buttons > div:last-child{display:block}.bx-mkb-note{display:block;margin:16px 0 10px;font-size:12px}.bx-mkb-note:first-of-type{margin-top:0}.bx-product-details-buttons{display:flex;gap:10px;flex-direction:row}.bx-product-details-buttons button{max-width:max-content;margin:10px 0 0 0;display:flex}@media (min-width:568px) and (max-height:480px){.bx-product-details-buttons{flex-direction:column}.bx-product-details-buttons button{margin:8px 0 0 10px}}`, PREF_HIDE_SECTIONS = getPref("ui_hide_sections"), selectorToHide = []; if (PREF_HIDE_SECTIONS.includes("news")) selectorToHide.push("#BodyContent > div[class*=CarouselRow-module]"); if (PREF_HIDE_SECTIONS.includes("all-games")) selectorToHide.push("#BodyContent div[class*=AllGamesRow-module__gridContainer]"), selectorToHide.push("#BodyContent div[class*=AllGamesRow-module__rowHeader]"); if (PREF_HIDE_SECTIONS.includes("most-popular")) selectorToHide.push('#BodyContent div[class*=HomePage-module__bottomSpacing]:has(a[href="/play/gallery/popular"])'); @@ -4942,11 +4888,11 @@ function addCss() { if (css += "div[class*=StreamMenu-module__menu]{min-width:100vw !important}", getPref("stream_simplify_menu")) css += "div[class*=Menu-module__scrollable]{--bxStreamMenuItemSize:80px;--streamMenuItemSize:calc(var(--bxStreamMenuItemSize) + 40px) !important}.bx-badges{top:calc(var(--streamMenuItemSize) - 20px)}body[data-media-type=tv] .bx-badges{top:calc(var(--streamMenuItemSize) - 10px) !important}button[class*=MenuItem-module__container]{min-width:auto !important;min-height:auto !important;width:var(--bxStreamMenuItemSize) !important;height:var(--bxStreamMenuItemSize) !important}div[class*=MenuItem-module__label]{display:none !important}svg[class*=MenuItem-module__icon]{width:36px;height:100% !important;padding:0 !important;margin:0 !important}"; else css += "body[data-media-type=tv] .bx-badges{top:calc(var(--streamMenuItemSize) + 30px)}body:not([data-media-type=tv]) .bx-badges{top:calc(var(--streamMenuItemSize) + 20px)}body:not([data-media-type=tv]) button[class*=MenuItem-module__container]{min-width:auto !important;width:100px !important}body:not([data-media-type=tv]) button[class*=MenuItem-module__container]:nth-child(n+2){margin-left:10px !important}body:not([data-media-type=tv]) div[class*=MenuItem-module__label]{margin-left:8px !important;margin-right:8px !important}"; if (getPref("ui_scrollbar_hide")) css += "html{scrollbar-width:none}body::-webkit-scrollbar{display:none}"; - const $style = CE("style", {}, css); + let $style = CE("style", {}, css); document.documentElement.appendChild($style); } function preloadFonts() { - const $link = CE("link", { + let $link = CE("link", { rel: "preload", href: "https://redphx.github.io/better-xcloud/fonts/promptfont.otf", as: "font", @@ -4975,7 +4921,7 @@ class MouseCursorHider { } } function patchHistoryMethod(type) { - const orig = window.history[type]; + let orig = window.history[type]; return function(...args) { return BxEvent.dispatch(window, BxEvent.POPSTATE, { arguments: args @@ -4985,32 +4931,31 @@ function patchHistoryMethod(type) { function onHistoryChanged(e) { if (e && e.arguments && e.arguments[0] && e.arguments[0].origin === "better-xcloud") return; window.setTimeout(RemotePlayManager.detect, 10); - const $settings = document.querySelector(".bx-settings-container"); + let $settings = document.querySelector(".bx-settings-container"); if ($settings) $settings.classList.add("bx-gone"); NavigationDialogManager.getInstance().hide(), LoadingScreen.reset(), window.setTimeout(HeaderSection.watchHeader, 2000), BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); } function setCodecPreferences(sdp, preferredCodec) { - const h264Pattern = /a=fmtp:(\d+).*profile-level-id=([0-9a-f]{6})/g, profilePrefix = preferredCodec === "high" ? "4d" : preferredCodec === "low" ? "420" : "42e", preferredCodecIds = [], matches = sdp.matchAll(h264Pattern) || []; + let h264Pattern = /a=fmtp:(\d+).*profile-level-id=([0-9a-f]{6})/g, profilePrefix = preferredCodec === "high" ? "4d" : preferredCodec === "low" ? "420" : "42e", preferredCodecIds = [], matches = sdp.matchAll(h264Pattern) || []; for (let match of matches) { - const id = match[1]; + let id = match[1]; if (match[2].startsWith(profilePrefix)) preferredCodecIds.push(id); } if (!preferredCodecIds.length) return sdp; - const lines = sdp.split("\r\n"); + let lines = sdp.split("\r\n"); for (let lineIndex = 0;lineIndex < lines.length; lineIndex++) { - const line = lines[lineIndex]; + let line = lines[lineIndex]; if (!line.startsWith("m=video")) continue; - const tmp = line.trim().split(" "); - let ids = tmp.slice(3); + let tmp = line.trim().split(" "), ids = tmp.slice(3); ids = ids.filter((item) => !preferredCodecIds.includes(item)), ids = preferredCodecIds.concat(ids), lines[lineIndex] = tmp.slice(0, 3).concat(ids).join(" "); break; } return lines.join("\r\n"); } function patchSdpBitrate(sdp, video, audio) { - const lines = sdp.split("\r\n"), mediaSet = new Set; + let lines = sdp.split("\r\n"), mediaSet = new Set; !!video && mediaSet.add("video"), !!audio && mediaSet.add("audio"); - const bitrate = { + let bitrate = { video, audio }; @@ -5023,7 +4968,7 @@ function patchSdpBitrate(sdp, video, audio) { break; } if (!media) continue; - const bLine = `b=AS:${bitrate[media]}`; + let bLine = `b=AS:${bitrate[media]}`; while (lineNumber++, lineNumber < lines.length) { if (line = lines[lineNumber], line.startsWith("i=") || line.startsWith("c=")) continue; if (line.startsWith("b=AS:")) { @@ -5061,7 +5006,7 @@ class WebGL2Player { animFrameId = null; constructor($video) { BxLogger.info(this.LOG_TAG, "Initialize"), this.$video = $video; - const $canvas = document.createElement("canvas"); + let $canvas = document.createElement("canvas"); $canvas.width = $video.videoWidth, $canvas.height = $video.videoHeight, this.$canvas = $canvas, this.setupShaders(), this.setupRendering(), $video.insertAdjacentElement("afterend", $canvas); } setFilter(filterId, update = !0) { @@ -5086,23 +5031,23 @@ class WebGL2Player { return this.$canvas; } updateCanvas() { - const gl = this.gl, program = this.program; + let gl = this.gl, program = this.program; gl.uniform2f(gl.getUniformLocation(program, "iResolution"), this.$canvas.width, this.$canvas.height), gl.uniform1i(gl.getUniformLocation(program, "filterId"), this.options.filterId), gl.uniform1f(gl.getUniformLocation(program, "sharpenFactor"), this.options.sharpenFactor), gl.uniform1f(gl.getUniformLocation(program, "brightness"), this.options.brightness), gl.uniform1f(gl.getUniformLocation(program, "contrast"), this.options.contrast), gl.uniform1f(gl.getUniformLocation(program, "saturation"), this.options.saturation); } drawFrame() { if (this.targetFps === 0) return; if (this.targetFps < 60) { - const currentTime = performance.now(); + let currentTime = performance.now(); if (currentTime - this.lastFrameTime < this.frameInterval) return; this.lastFrameTime = currentTime; } - const gl = this.gl; + let gl = this.gl; gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, this.$video), gl.drawArrays(gl.TRIANGLES, 0, 6); } setupRendering() { let animate; if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) { - const $video = this.$video; + let $video = this.$video; animate = () => { if (!this.stopped) this.drawFrame(), this.animFrameId = $video.requestVideoFrameCallback(animate); }, this.animFrameId = $video.requestVideoFrameCallback(animate); @@ -5112,23 +5057,23 @@ class WebGL2Player { } setupShaders() { BxLogger.info(this.LOG_TAG, "Setting up", getPref("video_power_preference")); - const gl = this.$canvas.getContext("webgl2", { + let gl = this.$canvas.getContext("webgl2", { isBx: !0, antialias: !0, alpha: !1, powerPreference: getPref("video_power_preference") }); this.gl = gl, gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferWidth); - const vShader = gl.createShader(gl.VERTEX_SHADER); + let vShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vShader, clarity_boost_default), gl.compileShader(vShader); - const fShader = gl.createShader(gl.FRAGMENT_SHADER); + let fShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fShader, clarity_boost_default2), gl.compileShader(fShader); - const program = gl.createProgram(); + let program = gl.createProgram(); if (this.program = program, gl.attachShader(program, vShader), gl.attachShader(program, fShader), gl.linkProgram(program), gl.useProgram(program), !gl.getProgramParameter(program, gl.LINK_STATUS)) console.error(`Link failed: ${gl.getProgramInfoLog(program)}`), console.error(`vs info-log: ${gl.getShaderInfoLog(vShader)}`), console.error(`fs info-log: ${gl.getShaderInfoLog(fShader)}`); this.updateCanvas(); - const buffer = gl.createBuffer(); + let buffer = gl.createBuffer(); this.resources.push(buffer), gl.bindBuffer(gl.ARRAY_BUFFER, buffer), gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW), gl.enableVertexAttribArray(0), gl.vertexAttribPointer(0, 2, gl.FLOAT, !1, 0, 0); - const texture = gl.createTexture(); + let texture = gl.createTexture(); this.resources.push(texture), gl.bindTexture(gl.TEXTURE_2D, texture), gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !0), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR), gl.uniform1i(gl.getUniformLocation(program, "data"), 0), gl.activeTexture(gl.TEXTURE0); } resume() { @@ -5143,7 +5088,7 @@ class WebGL2Player { } destroy() { BxLogger.info(this.LOG_TAG, "Destroy"), this.stop(); - const gl = this.gl; + let gl = this.gl; if (gl) { gl.getExtension("WEBGL_lose_context")?.loseContext(), gl.useProgram(null); for (let resource of this.resources) @@ -5172,9 +5117,9 @@ class StreamPlayer { this.$usmMatrix = this.$videoCss.querySelector("#bx-filter-usm-matrix"); return; } - const $fragment = document.createDocumentFragment(); + let $fragment = document.createDocumentFragment(); this.$videoCss = CE("style", { id: "bx-video-css" }), $fragment.appendChild(this.$videoCss); - const $svg = CE("svg", { + let $svg = CE("svg", { id: "bx-video-filters", xmlns: "http://www.w3.org/2000/svg", class: "bx-gone" @@ -5189,28 +5134,25 @@ class StreamPlayer { $fragment.appendChild($svg), document.documentElement.appendChild($fragment); } getVideoPlayerFilterStyle() { - const filters = [], sharpness = this.options.sharpness || 0; + let filters = [], sharpness = this.options.sharpness || 0; if (this.options.processing === "usm" && sharpness != 0) { - const matrix = `0 -1 0 -1 ${(7 - (sharpness / 2 - 1) * 0.5).toFixed(1)} -1 0 -1 0`; + let matrix = `0 -1 0 -1 ${(7 - (sharpness / 2 - 1) * 0.5).toFixed(1)} -1 0 -1 0`; this.$usmMatrix?.setAttributeNS(null, "kernelMatrix", matrix), filters.push("url(#bx-filter-usm)"); } - const saturation = this.options.saturation || 100; + let saturation = this.options.saturation || 100; if (saturation != 100) filters.push(`saturate(${saturation}%)`); - const contrast = this.options.contrast || 100; + let contrast = this.options.contrast || 100; if (contrast != 100) filters.push(`contrast(${contrast}%)`); - const brightness = this.options.brightness || 100; + let brightness = this.options.brightness || 100; if (brightness != 100) filters.push(`brightness(${brightness}%)`); return filters.join(" "); } resizePlayer() { - const PREF_RATIO = getPref("video_ratio"), $video = this.$video, isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport; - let $webGL2Canvas; + let PREF_RATIO = getPref("video_ratio"), $video = this.$video, isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport, $webGL2Canvas; if (this.playerType == "webgl2") $webGL2Canvas = this.webGL2Player?.getCanvas(); let targetWidth, targetHeight, targetObjectFit; if (PREF_RATIO.includes(":")) { - const tmp = PREF_RATIO.split(":"), videoRatio = parseFloat(tmp[0]) / parseFloat(tmp[1]); - let width = 0, height = 0; - const parentRect = $video.parentElement.getBoundingClientRect(); + let tmp = PREF_RATIO.split(":"), videoRatio = parseFloat(tmp[0]) / parseFloat(tmp[1]), width = 0, height = 0, parentRect = $video.parentElement.getBoundingClientRect(); if (parentRect.width / parentRect.height > videoRatio) height = parentRect.height, width = height * videoRatio; else width = parentRect.width, height = width / videoRatio; width = Math.ceil(Math.min(parentRect.width, width)), height = Math.ceil(Math.min(parentRect.height, height)), $video.dataset.width = width.toString(), $video.dataset.height = height.toString(), targetWidth = `${width}px`, targetHeight = `${height}px`, targetObjectFit = PREF_RATIO === "16:9" ? "contain" : "fill"; @@ -5220,7 +5162,7 @@ class StreamPlayer { } setPlayerType(type, refreshPlayer = !1) { if (this.playerType !== type) { - const videoClass = BX_FLAGS.DeviceInfo.deviceType === "android-tv" ? "bx-pixel" : "bx-gone"; + let videoClass = BX_FLAGS.DeviceInfo.deviceType === "android-tv" ? "bx-pixel" : "bx-gone"; if (type === "webgl2") { if (!this.webGL2Player) this.webGL2Player = new WebGL2Player(this.$video); else this.webGL2Player.resume(); @@ -5245,7 +5187,7 @@ class StreamPlayer { } refreshPlayer() { if (this.playerType === "webgl2") { - const options = this.options, webGL2Player = this.webGL2Player; + let options = this.options, webGL2Player = this.webGL2Player; if (options.processing === "usm") webGL2Player.setFilter(1); else webGL2Player.setFilter(2); webGL2Player.setSharpness(options.sharpness || 0), webGL2Player.setSaturation(options.saturation || 100), webGL2Player.setContrast(options.contrast || 100), webGL2Player.setBrightness(options.brightness || 100); @@ -5269,9 +5211,9 @@ class StreamPlayer { } } function patchVideoApi() { - const PREF_SKIP_SPLASH_VIDEO = getPref("skip_splash_video"), showFunc = function() { + let PREF_SKIP_SPLASH_VIDEO = getPref("skip_splash_video"), showFunc = function() { if (this.style.visibility = "visible", !this.videoWidth) return; - const playerOptions = { + let playerOptions = { processing: getPref("video_processing"), sharpness: getPref("video_sharpness"), saturation: getPref("video_saturation"), @@ -5287,7 +5229,7 @@ function patchVideoApi() { if (PREF_SKIP_SPLASH_VIDEO) return this.volume = 0, this.style.display = "none", this.dispatchEvent(new Event("ended")), new Promise(() => {}); return nativePlay.apply(this); } - const $parent = this.parentElement; + let $parent = this.parentElement; if (!this.src && $parent.dataset.testid === "media-container") this.addEventListener("loadedmetadata", showFunc, { once: !0 }); return nativePlay.apply(this); }; @@ -5297,16 +5239,16 @@ function patchRtcCodecs() { if (typeof RTCRtpTransceiver === "undefined" || !("setCodecPreferences" in RTCRtpTransceiver.prototype)) return !1; } function patchRtcPeerConnection() { - const nativeCreateDataChannel = RTCPeerConnection.prototype.createDataChannel; + let nativeCreateDataChannel = RTCPeerConnection.prototype.createDataChannel; RTCPeerConnection.prototype.createDataChannel = function() { - const dataChannel = nativeCreateDataChannel.apply(this, arguments); + let dataChannel = nativeCreateDataChannel.apply(this, arguments); return BxEvent.dispatch(window, BxEvent.DATA_CHANNEL_CREATED, { dataChannel }), dataChannel; }; - const maxVideoBitrate = getPref("bitrate_video_max"), codec = getPref("stream_codec_profile"); + let maxVideoBitrate = getPref("bitrate_video_max"), codec = getPref("stream_codec_profile"); if (codec !== "default" || maxVideoBitrate > 0) { - const nativeSetLocalDescription = RTCPeerConnection.prototype.setLocalDescription; + let nativeSetLocalDescription = RTCPeerConnection.prototype.setLocalDescription; RTCPeerConnection.prototype.setLocalDescription = function(description) { if (codec !== "default") arguments[0].sdp = setCodecPreferences(arguments[0].sdp, codec); try { @@ -5317,27 +5259,27 @@ function patchRtcPeerConnection() { return nativeSetLocalDescription.apply(this, arguments); }; } - const OrgRTCPeerConnection = window.RTCPeerConnection; + let OrgRTCPeerConnection = window.RTCPeerConnection; window.RTCPeerConnection = function() { - const conn = new OrgRTCPeerConnection; + let conn = new OrgRTCPeerConnection; return STATES.currentStream.peerConnection = conn, conn.addEventListener("connectionstatechange", (e) => { BxLogger.info("connectionstatechange", conn.connectionState); }), conn; }; } function patchAudioContext() { - const OrgAudioContext = window.AudioContext, nativeCreateGain = OrgAudioContext.prototype.createGain; + let OrgAudioContext = window.AudioContext, nativeCreateGain = OrgAudioContext.prototype.createGain; window.AudioContext = function(options) { if (options && options.latencyHint) options.latencyHint = 0; - const ctx = new OrgAudioContext(options); + let ctx = new OrgAudioContext(options); return BxLogger.info("patchAudioContext", ctx, options), ctx.createGain = function() { - const gainNode = nativeCreateGain.apply(this); + let gainNode = nativeCreateGain.apply(this); return gainNode.gain.value = getPref("audio_volume") / 100, STATES.currentStream.audioGainNode = gainNode, gainNode; }, STATES.currentStream.audioContext = ctx, ctx; }; } function patchMeControl() { - const overrideConfigs = { + let overrideConfigs = { enableAADTelemetry: !1, enableTelemetry: !1, telEvs: "", @@ -5374,7 +5316,7 @@ function disableAdobeAudienceManager() { window.adobe = Object.freeze({}); } function patchCanvasContext() { - const nativeGetContext = HTMLCanvasElement.prototype.getContext; + let nativeGetContext = HTMLCanvasElement.prototype.getContext; HTMLCanvasElement.prototype.getContext = function(contextType, contextAttributes) { if (contextType.includes("webgl")) { if (contextAttributes = contextAttributes || {}, !contextAttributes.isBx) { @@ -5401,9 +5343,9 @@ class ProductDetailsPage { tabIndex: 0, onClick: async (e) => { try { - const matches = /\/games\/(?[^\/]+)\/(?\w+)/.exec(window.location.pathname); + let matches = /\/games\/(?[^\/]+)\/(?\w+)/.exec(window.location.pathname); if (!matches?.groups) return; - const titleSlug = matches.groups.titleSlug.replaceAll("%" + "7C", "-"), productId = matches.groups.productId; + let titleSlug = matches.groups.titleSlug.replaceAll("%" + "7C", "-"), productId = matches.groups.productId; AppInterface.downloadWallpapers(titleSlug, productId); } catch (e2) {} } @@ -5412,7 +5354,7 @@ class ProductDetailsPage { static injectButtons() { if (!AppInterface) return; ProductDetailsPage.injectTimeoutId && clearTimeout(ProductDetailsPage.injectTimeoutId), ProductDetailsPage.injectTimeoutId = window.setTimeout(() => { - const $container = document.querySelector("div[class*=ActionButtons-module__container]"); + 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)); @@ -5427,18 +5369,17 @@ class StreamUiHandler { static observer; static cloneStreamHudButton($btnOrg, label, svgIcon) { if (!$btnOrg) return null; - const $container = $btnOrg.cloneNode(!0); - let timeout; + let $container = $btnOrg.cloneNode(!0), timeout; if (STATES.browser.capabilities.touch) { - const onTransitionStart = (e) => { + let onTransitionStart = (e) => { if (e.propertyName !== "opacity") return; timeout && clearTimeout(timeout), e.target.style.pointerEvents = "none"; }, onTransitionEnd = (e) => { if (e.propertyName !== "opacity") return; - const $streamHud = e.target.closest("#StreamHud"); + let $streamHud = e.target.closest("#StreamHud"); if (!$streamHud) return; if ($streamHud.style.left === "0px") { - const $target = e.target; + let $target = e.target; timeout && clearTimeout(timeout), timeout = window.setTimeout(() => { $target.style.pointerEvents = "auto"; }, 100); @@ -5446,21 +5387,21 @@ class StreamUiHandler { }; $container.addEventListener("transitionstart", onTransitionStart), $container.addEventListener("transitionend", onTransitionEnd); } - const $button = $container.querySelector("button"); + let $button = $container.querySelector("button"); if (!$button) return null; $button.setAttribute("title", label); - const $orgSvg = $button.querySelector("svg"); + let $orgSvg = $button.querySelector("svg"); if (!$orgSvg) return null; - const $svg = createSvgIcon(svgIcon); + let $svg = createSvgIcon(svgIcon); return $svg.style.fill = "none", $svg.setAttribute("class", $orgSvg.getAttribute("class") || ""), $svg.ariaHidden = "true", $orgSvg.replaceWith($svg), $container; } static cloneCloseButton($btnOrg, icon, className, onChange) { if (!$btnOrg) return null; - const $btn = $btnOrg.cloneNode(!0), $svg = createSvgIcon(icon); + let $btn = $btnOrg.cloneNode(!0), $svg = createSvgIcon(icon); return $svg.setAttribute("class", $btn.firstElementChild.getAttribute("class") || ""), $svg.style.fill = "none", $btn.classList.add(className), $btn.removeChild($btn.firstElementChild), $btn.appendChild($svg), $btn.addEventListener("click", onChange), $btn; } static async handleStreamMenu() { - const $btnCloseHud = document.querySelector("button[class*=StreamMenu-module__backButton]"); + let $btnCloseHud = document.querySelector("button[class*=StreamMenu-module__backButton]"); if (!$btnCloseHud) return; let { $btnRefresh, $btnHome } = StreamUiHandler; if (typeof $btnRefresh === "undefined") $btnRefresh = StreamUiHandler.cloneCloseButton($btnCloseHud, BxIcon.REFRESH, "bx-stream-refresh-button", () => { @@ -5473,29 +5414,27 @@ class StreamUiHandler { document.querySelector("div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]")?.appendChild(await StreamBadges.getInstance().render()); } static handleSystemMenu($streamHud) { - const $orgButton = $streamHud.querySelector("div[class^=HUDButton]"); + let $orgButton = $streamHud.querySelector("div[class^=HUDButton]"); if (!$orgButton) return; - const hideGripHandle = () => { - const $gripHandle = document.querySelector("#StreamHud button[class^=GripHandle]"); + let hideGripHandle = () => { + let $gripHandle = document.querySelector("#StreamHud button[class^=GripHandle]"); if ($gripHandle && $gripHandle.ariaExpanded === "true") $gripHandle.dispatchEvent(new PointerEvent("pointerdown")), $gripHandle.click(), $gripHandle.dispatchEvent(new PointerEvent("pointerdown")), $gripHandle.click(); - }; - let $btnStreamSettings = StreamUiHandler.$btnStreamSettings; + }, $btnStreamSettings = StreamUiHandler.$btnStreamSettings; if (typeof $btnStreamSettings === "undefined") $btnStreamSettings = StreamUiHandler.cloneStreamHudButton($orgButton, t("better-xcloud"), BxIcon.BETTER_XCLOUD), $btnStreamSettings?.addEventListener("click", (e) => { hideGripHandle(), e.preventDefault(), SettingsNavigationDialog.getInstance().show(); }), StreamUiHandler.$btnStreamSettings = $btnStreamSettings; - const streamStats = StreamStats.getInstance(); - let $btnStreamStats = StreamUiHandler.$btnStreamStats; + let streamStats = StreamStats.getInstance(), $btnStreamStats = StreamUiHandler.$btnStreamStats; if (typeof $btnStreamStats === "undefined") $btnStreamStats = StreamUiHandler.cloneStreamHudButton($orgButton, t("stream-stats"), BxIcon.STREAM_STATS), $btnStreamStats?.addEventListener("click", async (e) => { hideGripHandle(), e.preventDefault(), await streamStats.toggle(); - const btnStreamStatsOn = !streamStats.isHidden() && !streamStats.isGlancing(); + let btnStreamStatsOn = !streamStats.isHidden() && !streamStats.isGlancing(); $btnStreamStats.classList.toggle("bx-stream-menu-button-on", btnStreamStatsOn); }), StreamUiHandler.$btnStreamStats = $btnStreamStats; - const $btnParent = $orgButton.parentElement; + let $btnParent = $orgButton.parentElement; if ($btnStreamSettings && $btnStreamStats) { - const btnStreamStatsOn = !streamStats.isHidden() && !streamStats.isGlancing(); + let btnStreamStatsOn = !streamStats.isHidden() && !streamStats.isGlancing(); $btnStreamStats.classList.toggle("bx-stream-menu-button-on", btnStreamStatsOn), $btnParent.insertBefore($btnStreamStats, $btnParent.lastElementChild), $btnParent.insertBefore($btnStreamSettings, $btnStreamStats); } - const $dotsButton = $btnParent.lastElementChild; + let $dotsButton = $btnParent.lastElementChild; $dotsButton.parentElement.insertBefore($dotsButton, $dotsButton.parentElement.firstElementChild); } static reset() { @@ -5503,16 +5442,16 @@ class StreamUiHandler { } static observe() { StreamUiHandler.reset(); - const $screen = document.querySelector("#PageContent section[class*=PureScreens]"); + let $screen = document.querySelector("#PageContent section[class*=PureScreens]"); if (!$screen) return; - const observer = new MutationObserver((mutationList) => { + let observer = new MutationObserver((mutationList) => { mutationList.forEach((item) => { if (item.type !== "childList") return; item.addedNodes.forEach(async ($node) => { if (!$node || $node.nodeType !== Node.ELEMENT_NODE) return; let $elm = $node; if (!($elm instanceof HTMLElement)) return; - const className = $elm.className || ""; + let className = $elm.className || ""; if (className.includes("PureErrorPage")) { BxEvent.dispatch(window, BxEvent.STREAM_ERROR_PAGE); return; @@ -5535,54 +5474,16 @@ class XboxApi { static async getProductTitle(xboxTitleId) { if (xboxTitleId = xboxTitleId.toString(), XboxApi.CACHED_TITLES[xboxTitleId]) return XboxApi.CACHED_TITLES[xboxTitleId]; try { - const url = `https://displaycatalog.mp.microsoft.com/v7.0/products/lookup?market=US&languages=en&value=${xboxTitleId}&alternateId=XboxTitleId&fieldsTemplate=browse`, productTitle = (await (await NATIVE_FETCH(url)).json()).Products[0].LocalizedProperties[0].ProductTitle; + let url = `https://displaycatalog.mp.microsoft.com/v7.0/products/lookup?market=US&languages=en&value=${xboxTitleId}&alternateId=XboxTitleId&fieldsTemplate=browse`, productTitle = (await (await NATIVE_FETCH(url)).json()).Products[0].LocalizedProperties[0].ProductTitle; return XboxApi.CACHED_TITLES[xboxTitleId] = productTitle, productTitle; } catch (e) {} return null; } } -function unload() { - if (!STATES.isPlaying) return; - STATES.currentStream.streamPlayer?.destroy(), STATES.isPlaying = !1, STATES.currentStream = {}, window.BX_EXPOSED.shouldShowSensorControls = !1, window.BX_EXPOSED.stopTakRendering = !1, NavigationDialogManager.getInstance().hide(), StreamStats.getInstance().onStoppedPlaying(); -} -function observeRootDialog($root) { - let beingShown = !1; - new MutationObserver((mutationList) => { - for (let mutation of mutationList) { - if (mutation.type !== "childList") continue; - if (BX_FLAGS.Debug && BxLogger.warning("RootDialog", "added", mutation.addedNodes), mutation.addedNodes.length === 1) { - const $addedElm = mutation.addedNodes[0]; - if ($addedElm instanceof HTMLElement && $addedElm.className) { - if ($root.querySelector("div[class*=GuideDialog]")) GuideMenu.observe($addedElm); - } - } - const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0); - if (shown !== beingShown) beingShown = shown, BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_DIALOG_DISMISSED); - } - }).observe($root, { subtree: !0, childList: !0 }); -} -function waitForRootDialog() { - const observer = new MutationObserver((mutationList) => { - for (let mutation of mutationList) { - if (mutation.type !== "childList") continue; - const $target = mutation.target; - if ($target.id && $target.id === "gamepass-dialog-root") { - observer.disconnect(), observeRootDialog($target); - break; - } - } - }); - observer.observe(document.documentElement, { subtree: !0, childList: !0 }); -} -function main() { - if (getPref("game_msfs2020_force_native_mkb")) BX_FLAGS.ForceNativeMkbTitles.push("9PMQDM08SNK9"); - if (patchRtcPeerConnection(), patchRtcCodecs(), interceptHttpRequests(), patchVideoApi(), patchCanvasContext(), getPref("audio_enable_volume_control") && patchAudioContext(), getPref("block_tracking")) patchMeControl(), disableAdobeAudienceManager(); - if (waitForRootDialog(), addCss(), Toast.setup(), GuideMenu.addEventListeners(), StreamStatsCollector.setupEvents(), StreamBadges.setupEvents(), StreamStats.setupEvents(), getPref("controller_show_connection_status")) window.addEventListener("gamepadconnected", (e) => showGamepadToast(e.gamepad)), window.addEventListener("gamepaddisconnected", (e) => showGamepadToast(e.gamepad)); -} if (window.location.pathname.includes("/auth/msa")) { - const nativePushState = window.history.pushState; + let nativePushState = window.history.pushState; throw window.history.pushState = function(...args) { - const url = args[2]; + let url = args[2]; if (url && (url.startsWith("/play") || url.substring(6).startsWith("/play"))) { console.log("Redirecting to xbox.com/play"), window.stop(), window.location.href = "https://www.xbox.com" + url; return; @@ -5601,7 +5502,7 @@ document.addEventListener("readystatechange", (e) => { if (STATES.isSignedIn = !!window.xbcUser?.isSignedIn, STATES.isSignedIn) getPref("xhome_enabled") && RemotePlayManager.getInstance().initialize(); else window.setTimeout(HeaderSection.watchHeader, 2000); if (getPref("ui_hide_sections").includes("friends")) { - const $parent = document.querySelector("div[class*=PlayWithFriendsSkeleton]")?.closest("div[class*=HomePage-module]"); + let $parent = document.querySelector("div[class*=PlayWithFriendsSkeleton]")?.closest("div[class*=HomePage-module]"); $parent && ($parent.style.display = "none"); } preloadFonts(); @@ -5635,23 +5536,61 @@ window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, (e) => { if (e.component === "product-details") ProductDetailsPage.injectButtons(); }); window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => { - const dataChannel = e.dataChannel; + let dataChannel = e.dataChannel; if (!dataChannel || dataChannel.label !== "message") return; dataChannel.addEventListener("message", async (msg) => { if (msg.origin === "better-xcloud" || typeof msg.data !== "string") return; if (msg.data.includes("/titleinfo")) { - const json = JSON.parse(JSON.parse(msg.data).content), xboxTitleId = parseInt(json.titleid, 16); + let json = JSON.parse(JSON.parse(msg.data).content), xboxTitleId = parseInt(json.titleid, 16); if (STATES.currentStream.xboxTitleId = xboxTitleId, STATES.remotePlay.isPlaying) { if (STATES.currentStream.titleSlug = "remote-play", json.focused) { - const productTitle = await XboxApi.getProductTitle(xboxTitleId); + let productTitle = await XboxApi.getProductTitle(xboxTitleId); if (productTitle) STATES.currentStream.titleSlug = productTitleToSlug(productTitle); } } } }); }); +function unload() { + if (!STATES.isPlaying) return; + STATES.currentStream.streamPlayer?.destroy(), STATES.isPlaying = !1, STATES.currentStream = {}, window.BX_EXPOSED.shouldShowSensorControls = !1, window.BX_EXPOSED.stopTakRendering = !1, NavigationDialogManager.getInstance().hide(), StreamStats.getInstance().onStoppedPlaying(); +} window.addEventListener(BxEvent.STREAM_STOPPED, unload); window.addEventListener("pagehide", (e) => { BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); }); +function observeRootDialog($root) { + let beingShown = !1; + new MutationObserver((mutationList) => { + for (let mutation of mutationList) { + if (mutation.type !== "childList") continue; + if (BX_FLAGS.Debug && BxLogger.warning("RootDialog", "added", mutation.addedNodes), mutation.addedNodes.length === 1) { + let $addedElm = mutation.addedNodes[0]; + if ($addedElm instanceof HTMLElement && $addedElm.className) { + if ($root.querySelector("div[class*=GuideDialog]")) GuideMenu.observe($addedElm); + } + } + let shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0); + if (shown !== beingShown) beingShown = shown, BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_DIALOG_DISMISSED); + } + }).observe($root, { subtree: !0, childList: !0 }); +} +function waitForRootDialog() { + let observer = new MutationObserver((mutationList) => { + for (let mutation of mutationList) { + if (mutation.type !== "childList") continue; + let $target = mutation.target; + if ($target.id && $target.id === "gamepass-dialog-root") { + observer.disconnect(), observeRootDialog($target); + break; + } + } + }); + observer.observe(document.documentElement, { subtree: !0, childList: !0 }); +} +function main() { + if (getPref("game_msfs2020_force_native_mkb")) BX_FLAGS.ForceNativeMkbTitles.push("9PMQDM08SNK9"); + if (patchRtcPeerConnection(), patchRtcCodecs(), interceptHttpRequests(), patchVideoApi(), patchCanvasContext(), getPref("audio_enable_volume_control") && patchAudioContext(), getPref("block_tracking")) patchMeControl(), disableAdobeAudienceManager(); + if (waitForRootDialog(), addCss(), Toast.setup(), GuideMenu.addEventListeners(), StreamStatsCollector.setupEvents(), StreamBadges.setupEvents(), StreamStats.setupEvents(), getPref("controller_show_connection_status")) window.addEventListener("gamepadconnected", (e) => showGamepadToast(e.gamepad)), window.addEventListener("gamepaddisconnected", (e) => showGamepadToast(e.gamepad)); +} main(); diff --git a/dist/better-xcloud.user.js b/dist/better-xcloud.user.js index 5953302..66993ba 100644 --- a/dist/better-xcloud.user.js +++ b/dist/better-xcloud.user.js @@ -42,7 +42,7 @@ BxLogger.info("BxFlags", BX_FLAGS); var NATIVE_FETCH = window.fetch; var SMART_TV_UNIQUE_ID = "FC4A1DA2-711C-4E9C-BC7F-047AF8A672EA", CHROMIUM_VERSION = "123.0.0.0"; if (!!window.chrome || window.navigator.userAgent.includes("Chrome")) { - const match = window.navigator.userAgent.match(/\s(?:Chrome|Edg)\/([\d\.]+)/); + let match = window.navigator.userAgent.match(/\s(?:Chrome|Edg)\/([\d\.]+)/); if (match) CHROMIUM_VERSION = match[1]; } class UserAgent { @@ -64,7 +64,7 @@ class UserAgent { UserAgent.spoof(); } static updateStorage(profile, custom) { - const config = UserAgent.#config; + let config = UserAgent.#config; if (config.profile = profile, profile === "custom" && typeof custom !== "undefined") config.custom = custom; window.localStorage.setItem(UserAgent.STORAGE_KEY, JSON.stringify(config)); } @@ -72,7 +72,7 @@ class UserAgent { return window.navigator.orgUserAgent || window.navigator.userAgent; } static get(profile) { - const defaultUserAgent = window.navigator.userAgent; + let defaultUserAgent = window.navigator.userAgent; switch (profile) { case "default": return defaultUserAgent; @@ -84,22 +84,21 @@ class UserAgent { } static isSafari() { if (this.#isSafari !== null) return this.#isSafari; - const userAgent = UserAgent.getDefault().toLowerCase(); - let result = userAgent.includes("safari") && !userAgent.includes("chrom"); + let userAgent = UserAgent.getDefault().toLowerCase(), result = userAgent.includes("safari") && !userAgent.includes("chrom"); return this.#isSafari = result, result; } static isSafariMobile() { if (this.#isSafariMobile !== null) return this.#isSafariMobile; - const userAgent = UserAgent.getDefault().toLowerCase(), result = this.isSafari() && userAgent.includes("mobile"); + let userAgent = UserAgent.getDefault().toLowerCase(), result = this.isSafari() && userAgent.includes("mobile"); return this.#isSafariMobile = result, result; } static isMobile() { if (this.#isMobile !== null) return this.#isMobile; - const userAgent = UserAgent.getDefault().toLowerCase(), result = /iphone|ipad|android/.test(userAgent); + let userAgent = UserAgent.getDefault().toLowerCase(), result = /iphone|ipad|android/.test(userAgent); return this.#isMobile = result, result; } static spoof() { - const profile = UserAgent.#config.profile; + let profile = UserAgent.#config.profile; if (profile === "default") return; let newUserAgent = UserAgent.get(profile); if ("userAgentData" in window.navigator) window.navigator.orgUserAgentData = window.navigator.userAgentData, Object.defineProperty(window.navigator, "userAgentData", {}); @@ -108,11 +107,6 @@ class UserAgent { }); } } -function deepClone(obj) { - if ("structuredClone" in window) return structuredClone(obj); - if (!obj) return {}; - return JSON.parse(JSON.stringify(obj)); -} var SCRIPT_VERSION = "5.8.6-beta", 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, supportMkb = AppInterface || !userAgent.match(/(android|iphone|ipad)/), STATES = { @@ -140,6 +134,11 @@ var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.inclu remotePlay: {}, pointerServerPort: 9269 }, STORAGE = {}; +function deepClone(obj) { + if ("structuredClone" in window) return structuredClone(obj); + if (!obj) return {}; + return JSON.parse(JSON.stringify(obj)); +} var BxEvent; ((BxEvent) => { BxEvent.JUMP_BACK_IN_READY = "bx-jump-back-in-ready", BxEvent.POPSTATE = "bx-popstate", BxEvent.TITLE_INFO_READY = "bx-title-info-ready", BxEvent.SETTINGS_CHANGED = "bx-settings-changed", BxEvent.STREAM_LOADING = "bx-stream-loading", BxEvent.STREAM_STARTING = "bx-stream-starting", BxEvent.STREAM_STARTED = "bx-stream-started", BxEvent.STREAM_PLAYING = "bx-stream-playing", BxEvent.STREAM_STOPPED = "bx-stream-stopped", BxEvent.STREAM_ERROR_PAGE = "bx-stream-error-page", BxEvent.STREAM_WEBRTC_CONNECTED = "bx-stream-webrtc-connected", BxEvent.STREAM_WEBRTC_DISCONNECTED = "bx-stream-webrtc-disconnected", BxEvent.STREAM_SESSION_READY = "bx-stream-session-ready", BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED = "bx-custom-touch-layouts-loaded", BxEvent.TOUCH_LAYOUT_MANAGER_READY = "bx-touch-layout-manager-ready", BxEvent.REMOTE_PLAY_READY = "bx-remote-play-ready", BxEvent.REMOTE_PLAY_FAILED = "bx-remote-play-failed", BxEvent.XCLOUD_SERVERS_READY = "bx-servers-ready", BxEvent.XCLOUD_SERVERS_UNAVAILABLE = "bx-servers-unavailable", BxEvent.DATA_CHANNEL_CREATED = "bx-data-channel-created", BxEvent.GAME_BAR_ACTION_ACTIVATED = "bx-game-bar-action-activated", BxEvent.MICROPHONE_STATE_CHANGED = "bx-microphone-state-changed", BxEvent.SPEAKER_STATE_CHANGED = "bx-speaker-state-changed", BxEvent.CAPTURE_SCREENSHOT = "bx-capture-screenshot", BxEvent.POINTER_LOCK_REQUESTED = "bx-pointer-lock-requested", BxEvent.POINTER_LOCK_EXITED = "bx-pointer-lock-exited", BxEvent.NAVIGATION_FOCUS_CHANGED = "bx-nav-focus-changed", BxEvent.XCLOUD_DIALOG_SHOWN = "bx-xcloud-dialog-shown", BxEvent.XCLOUD_DIALOG_DISMISSED = "bx-xcloud-dialog-dismissed", BxEvent.XCLOUD_GUIDE_MENU_SHOWN = "bx-xcloud-guide-menu-shown", BxEvent.XCLOUD_POLLING_MODE_CHANGED = "bx-xcloud-polling-mode-changed", BxEvent.XCLOUD_RENDERING_COMPONENT = "bx-xcloud-rendering-component", BxEvent.XCLOUD_ROUTER_HISTORY_READY = "bx-xcloud-router-history-ready"; @@ -149,7 +148,7 @@ var BxEvent; alert("BxEvent.dispatch(): eventName is null"); return; } - const event = new Event(eventName); + let event = new Event(eventName); if (data) for (let key in data) event[key] = data[key]; target.dispatchEvent(event), AppInterface && AppInterface.onEvent(eventName), BX_FLAGS.Debug && BxLogger.warning("BxEvent", "dispatch", eventName, data); @@ -166,69 +165,6 @@ class NavigationUtils { } } var setNearby = NavigationUtils.setNearby; -function createElement(elmName, props = {}, ..._) { - let $elm; - const hasNs = "xmlns" in props; - if (hasNs) $elm = document.createElementNS(props.xmlns, elmName), delete props.xmlns; - else $elm = document.createElement(elmName); - if (props._nearby) setNearby($elm, props._nearby), delete props._nearby; - for (let key in props) { - if ($elm.hasOwnProperty(key)) continue; - if (hasNs) $elm.setAttributeNS(null, key, props[key]); - else if (key === "on") for (let eventName in props[key]) - $elm.addEventListener(eventName, props[key][eventName]); - else $elm.setAttribute(key, props[key]); - } - for (let i = 2, size = arguments.length;i < size; i++) { - const arg = arguments[i]; - if (arg instanceof Node) $elm.appendChild(arg); - else if (arg !== null && arg !== !1 && typeof arg !== "undefined") $elm.appendChild(document.createTextNode(arg)); - } - return $elm; -} -function getReactProps($elm) { - for (let key in $elm) - if (key.startsWith("__reactProps")) return $elm[key]; - return null; -} -function escapeHtml(html) { - const text = document.createTextNode(html), $span = document.createElement("span"); - return $span.appendChild(text), $span.innerHTML; -} -function isElementVisible($elm) { - const rect = $elm.getBoundingClientRect(); - return (rect.x >= 0 || rect.y >= 0) && !!rect.width && !!rect.height; -} -function removeChildElements($parent) { - while ($parent.firstElementChild) - $parent.firstElementChild.remove(); -} -function clearFocus() { - if (document.activeElement instanceof HTMLElement) document.activeElement.blur(); -} -function clearDataSet($elm) { - Object.keys($elm.dataset).forEach((key) => { - delete $elm.dataset[key]; - }); -} -function humanFileSize(size) { - const i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); - return (size / Math.pow(1024, i)).toFixed(2) + " " + FILE_SIZE_UNITS[i]; -} -function secondsToHm(seconds) { - let h = Math.floor(seconds / 3600), m = Math.floor(seconds % 3600 / 60) + 1; - if (m === 60) h += 1, m = 0; - const output = []; - return h > 0 && output.push(`${h}h`), m > 0 && output.push(`${m}m`), output.join(" "); -} -function secondsToHms(seconds) { - let h = Math.floor(seconds / 3600); - seconds %= 3600; - let m = Math.floor(seconds / 60), s = seconds % 60; - const output = []; - if (h > 0 && output.push(`${h}h`), m > 0 && output.push(`${m}m`), s > 0 || output.length === 0) output.push(`${s}s`); - return output.join(" "); -} var ButtonStyleClass = { 1: "bx-primary", 2: "bx-danger", @@ -242,22 +178,85 @@ var ButtonStyleClass = { 512: "bx-circular", 1024: "bx-normal-case", 2048: "bx-normal-link" -}, CE = createElement, svgParser = (svg) => new DOMParser().parseFromString(svg, "image/svg+xml").documentElement, createSvgIcon = (icon) => { +}; +function createElement(elmName, props = {}, ..._) { + let $elm, hasNs = "xmlns" in props; + if (hasNs) $elm = document.createElementNS(props.xmlns, elmName), delete props.xmlns; + else $elm = document.createElement(elmName); + if (props._nearby) setNearby($elm, props._nearby), delete props._nearby; + for (let key in props) { + if ($elm.hasOwnProperty(key)) continue; + if (hasNs) $elm.setAttributeNS(null, key, props[key]); + else if (key === "on") for (let eventName in props[key]) + $elm.addEventListener(eventName, props[key][eventName]); + else $elm.setAttribute(key, props[key]); + } + for (let i = 2, size = arguments.length;i < size; i++) { + let arg = arguments[i]; + if (arg instanceof Node) $elm.appendChild(arg); + else if (arg !== null && arg !== !1 && typeof arg !== "undefined") $elm.appendChild(document.createTextNode(arg)); + } + return $elm; +} +var CE = createElement, svgParser = (svg) => new DOMParser().parseFromString(svg, "image/svg+xml").documentElement, createSvgIcon = (icon) => { return svgParser(icon.toString()); }, ButtonStyleIndices = Object.keys(ButtonStyleClass).map((i) => parseInt(i)), createButton = (options) => { let $btn; if (options.url) $btn = CE("a", { class: "bx-button" }), $btn.href = options.url, $btn.target = "_blank"; else $btn = CE("button", { class: "bx-button", type: "button" }); - const style = options.style || 0; + let style = options.style || 0; style && ButtonStyleIndices.forEach((index) => { style & index && $btn.classList.add(ButtonStyleClass[index]); }), options.classes && $btn.classList.add(...options.classes), options.icon && $btn.appendChild(createSvgIcon(options.icon)), options.label && $btn.appendChild(CE("span", {}, options.label)), options.title && $btn.setAttribute("title", options.title), options.disabled && ($btn.disabled = !0), options.onClick && $btn.addEventListener("click", options.onClick), $btn.tabIndex = typeof options.tabIndex === "number" ? options.tabIndex : 0; for (let key in options.attributes) if (!$btn.hasOwnProperty(key)) $btn.setAttribute(key, options.attributes[key]); return $btn; -}, CTN = document.createTextNode.bind(document); +}; +function getReactProps($elm) { + for (let key in $elm) + if (key.startsWith("__reactProps")) return $elm[key]; + return null; +} +function escapeHtml(html) { + let text = document.createTextNode(html), $span = document.createElement("span"); + return $span.appendChild(text), $span.innerHTML; +} +function isElementVisible($elm) { + let rect = $elm.getBoundingClientRect(); + return (rect.x >= 0 || rect.y >= 0) && !!rect.width && !!rect.height; +} +var CTN = document.createTextNode.bind(document); window.BX_CE = createElement; +function removeChildElements($parent) { + while ($parent.firstElementChild) + $parent.firstElementChild.remove(); +} +function clearFocus() { + if (document.activeElement instanceof HTMLElement) document.activeElement.blur(); +} +function clearDataSet($elm) { + Object.keys($elm.dataset).forEach((key) => { + delete $elm.dataset[key]; + }); +} var FILE_SIZE_UNITS = ["B", "KB", "MB", "GB", "TB"]; +function humanFileSize(size) { + let i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); + return (size / Math.pow(1024, i)).toFixed(2) + " " + FILE_SIZE_UNITS[i]; +} +function secondsToHm(seconds) { + let h = Math.floor(seconds / 3600), m = Math.floor(seconds % 3600 / 60) + 1; + if (m === 60) h += 1, m = 0; + let output = []; + return h > 0 && output.push(`${h}h`), m > 0 && output.push(`${m}m`), output.join(" "); +} +function secondsToHms(seconds) { + let h = Math.floor(seconds / 3600); + seconds %= 3600; + let m = Math.floor(seconds / 60), s = seconds % 60, output = []; + if (h > 0 && output.push(`${h}h`), m > 0 && output.push(`${m}m`), s > 0 || output.length === 0) output.push(`${s}s`); + return output.join(" "); +} var SUPPORTED_LANGUAGES = { "en-US": "English (US)", "ca-CA": "Català", @@ -642,7 +641,7 @@ class Translations { let locale; if (newLocale) localStorage.setItem(Translations.#KEY_LOCALE, newLocale), locale = newLocale; else locale = localStorage.getItem(Translations.#KEY_LOCALE); - const supportedLocales = Translations.#supportedLocales; + let supportedLocales = Translations.#supportedLocales; if (!locale) { if (locale = window.navigator.language || Translations.#EN_US, supportedLocales.indexOf(locale) === -1) locale = Translations.#EN_US; localStorage.setItem(Translations.#KEY_LOCALE, locale); @@ -674,7 +673,7 @@ class Translations { } static async downloadTranslations(locale) { try { - const translations = await (await NATIVE_FETCH(`https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/translations/${locale}.json`)).json(); + let translations = await (await NATIVE_FETCH(`https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/translations/${locale}.json`)).json(); if (localStorage.getItem(Translations.#KEY_LOCALE) === locale) window.localStorage.setItem(Translations.#KEY_TRANSLATIONS, JSON.stringify(translations)), Translations.#foreignTranslations = translations; return !0; } catch (e) { @@ -708,63 +707,62 @@ var BypassServers = { }; class SettingElement { static #renderOptions(key, setting, currentValue, onChange) { - const $control = CE("select", { + let $control = CE("select", { tabindex: 0 - }); - let $parent; + }), $parent; if (setting.optionsGroup) $parent = CE("optgroup", { label: setting.optionsGroup }), $control.appendChild($parent); else $parent = $control; for (let value in setting.options) { - const label = setting.options[value], $option = CE("option", { value }, label); + let label = setting.options[value], $option = CE("option", { value }, label); $parent.appendChild($option); } return $control.value = currentValue, onChange && $control.addEventListener("input", (e) => { - const target = e.target, value = setting.type && setting.type === "number" ? parseInt(target.value) : target.value; + let target = e.target, value = setting.type && setting.type === "number" ? parseInt(target.value) : target.value; !e.ignoreOnChange && onChange(e, value); }), $control.setValue = (value) => { $control.value = value; }, $control; } static #renderMultipleOptions(key, setting, currentValue, onChange, params = {}) { - const $control = CE("select", { + let $control = CE("select", { multiple: !0, tabindex: 0 }); if (params && params.size) $control.setAttribute("size", params.size.toString()); for (let value in setting.multipleOptions) { - const label = setting.multipleOptions[value], $option = CE("option", { value }, label); + let label = setting.multipleOptions[value], $option = CE("option", { value }, label); $option.selected = currentValue.indexOf(value) > -1, $option.addEventListener("mousedown", function(e) { e.preventDefault(); - const target = e.target; + let target = e.target; target.selected = !target.selected; - const $parent = target.parentElement; + let $parent = target.parentElement; $parent.focus(), BxEvent.dispatch($parent, "input"); }), $control.appendChild($option); } return $control.addEventListener("mousedown", function(e) { - const self = this, orgScrollTop = self.scrollTop; + let self = this, orgScrollTop = self.scrollTop; window.setTimeout(() => self.scrollTop = orgScrollTop, 0); }), $control.addEventListener("mousemove", (e) => e.preventDefault()), onChange && $control.addEventListener("input", (e) => { - const target = e.target, values = Array.from(target.selectedOptions).map((i) => i.value); + let target = e.target, values = Array.from(target.selectedOptions).map((i) => i.value); !e.ignoreOnChange && onChange(e, values); }), $control; } static #renderNumber(key, setting, currentValue, onChange) { - const $control = CE("input", { + let $control = CE("input", { tabindex: 0, type: "number", min: setting.min, max: setting.max }); return $control.value = currentValue, onChange && $control.addEventListener("input", (e) => { - const target = e.target, value = Math.max(setting.min, Math.min(setting.max, parseInt(target.value))); + let target = e.target, value = Math.max(setting.min, Math.min(setting.max, parseInt(target.value))); target.value = value.toString(), !e.ignoreOnChange && onChange(e, value); }), $control; } static #renderCheckbox(key, setting, currentValue, onChange) { - const $control = CE("input", { type: "checkbox", tabindex: 0 }); + let $control = CE("input", { type: "checkbox", tabindex: 0 }); return $control.checked = currentValue, onChange && $control.addEventListener("input", (e) => { !e.ignoreOnChange && onChange(e, e.target.checked); }), $control.setValue = (value) => { @@ -773,8 +771,7 @@ class SettingElement { } static #renderNumberStepper(key, setting, value, onChange, options = {}) { options = options || {}, options.suffix = options.suffix || "", options.disabled = !!options.disabled, options.hideSlider = !!options.hideSlider; - let $text, $btnDec, $btnInc, $range = null, controlValue = value; - const { min: MIN, max: MAX } = setting, STEPS = Math.max(setting.steps || 1, 1), renderTextValue = (value2) => { + let $text, $btnDec, $btnInc, $range = null, controlValue = value, MIN = setting.min, MAX = setting.max, STEPS = Math.max(setting.steps || 1, 1), renderTextValue = (value2) => { value2 = parseInt(value2); let textContent = null; if (options.customTextValue) textContent = options.customTextValue(value2); @@ -808,7 +805,7 @@ class SettingElement { }), $wrapper.addEventListener("input", (e) => { BxEvent.dispatch($range, "input"); }), $wrapper.appendChild($range), options.ticks || options.exactTicks) { - const markersId = `markers-${key}`, $markers = CE("datalist", { id: markersId }); + let markersId = `markers-${key}`, $markers = CE("datalist", { id: markersId }); if ($range.setAttribute("list", markersId), options.exactTicks) { let start = Math.max(Math.floor(MIN / options.exactTicks), 1) * options.exactTicks; if (start === MIN) start += options.exactTicks; @@ -819,20 +816,18 @@ class SettingElement { $wrapper.appendChild($markers); } updateButtonsVisibility(); - let interval, isHolding = !1; - const onClick = (e) => { + let interval, isHolding = !1, onClick = (e) => { if (isHolding) { e.preventDefault(), isHolding = !1; return; } - const $btn = e.target; - let value2 = parseInt(controlValue); + let $btn = e.target, value2 = parseInt(controlValue); if ($btn.dataset.type === "dec") value2 = Math.max(MIN, value2 - STEPS); else value2 = Math.min(MAX, value2 + STEPS); controlValue = value2, updateButtonsVisibility(), $text.textContent = renderTextValue(value2), $range && ($range.value = value2.toString()), isHolding = !1, !e.ignoreOnChange && onChange && onChange(e, value2); }, onMouseDown = (e) => { e.preventDefault(), isHolding = !0; - const args = arguments; + let args = arguments; interval && clearInterval(interval), interval = window.setInterval(() => { e.target && BxEvent.dispatch(e.target, "click", { arguments: args @@ -855,14 +850,13 @@ class SettingElement { checkbox: SettingElement.#renderCheckbox }; static render(type, key, setting, currentValue, onChange, options) { - const method = SettingElement.#METHOD_MAP[type], $control = method(...Array.from(arguments).slice(1)); + let method = SettingElement.#METHOD_MAP[type], $control = method(...Array.from(arguments).slice(1)); if (type !== "number-stepper") $control.id = `bx_setting_${key}`; if (type === "options" || type === "multiple-options") $control.name = $control.id; return $control; } static fromPref(key, storage, onChange, overrideParams = {}) { - const definition = storage.getDefinition(key); - let currentValue = storage.getSetting(key), type; + let definition = storage.getDefinition(key), currentValue = storage.getSetting(key), type; if ("type" in definition) type = definition.type; else if ("options" in definition) type = "options"; else if ("multipleOptions" in definition) type = "multiple-options"; @@ -885,7 +879,7 @@ class BaseSettingsStore { this.storage = window.localStorage, this.storageKey = storageKey; let settingId; for (settingId in definitions) { - const setting = definitions[settingId]; + let setting = definitions[settingId]; if (typeof setting.requiredVariants === "string") setting.requiredVariants = [setting.requiredVariants]; setting.ready && setting.ready.call(this, setting); } @@ -893,12 +887,12 @@ class BaseSettingsStore { } get settings() { if (this._settings) return this._settings; - const settings = JSON.parse(this.storage.getItem(this.storageKey) || "{}"); + let settings = JSON.parse(this.storage.getItem(this.storageKey) || "{}"); return this._settings = settings, settings; } getDefinition(key) { if (!this.definitions[key]) { - const error = "Request invalid definition: " + key; + let error = "Request invalid definition: " + key; throw alert(error), Error(error); } return this.definitions[key]; @@ -908,7 +902,7 @@ class BaseSettingsStore { debugger; return; } - const definition = this.definitions[key]; + let definition = this.definitions[key]; if (definition.requiredVariants && !definition.requiredVariants.includes(SCRIPT_VARIANT)) return definition.default; if (checkUnsupported && definition.unsupported) return definition.default; if (!(key in this.settings)) this.settings[key] = this.validateValue(key, null); @@ -925,7 +919,7 @@ class BaseSettingsStore { this.storage.setItem(this.storageKey, JSON.stringify(this.settings)); } validateValue(key, value) { - const def = this.definitions[key]; + let def = this.definitions[key]; if (!def) return value; if (typeof value === "undefined" || value === null) value = def.default; if ("min" in def) value = Math.max(def.min, value); @@ -933,7 +927,7 @@ class BaseSettingsStore { if ("options" in def && !(value in def.options)) value = def.default; else if ("multipleOptions" in def) { if (value.length) { - const validOptions = Object.keys(def.multipleOptions); + let validOptions = Object.keys(def.multipleOptions); value.forEach((item2, idx) => { validOptions.indexOf(item2) === -1 && value.splice(idx, 1); }); @@ -946,16 +940,16 @@ class BaseSettingsStore { return this.definitions[key].label || key; } getValueText(key, value) { - const definition = this.definitions[key]; + let definition = this.definitions[key]; if (definition.type === "number-stepper") { - const params = definition.params; + let params = definition.params; if (params.customTextValue) { - const text = params.customTextValue(value); + let text = params.customTextValue(value); if (text) return text; } return value.toString(); } else if ("options" in definition) { - const options = definition.options; + let options = definition.options; if (value in options) return options[value]; } else if (typeof value === "boolean") return value ? t("on") : t("off"); return value.toString(); @@ -986,7 +980,7 @@ class StreamStatsCollector { fps: { current: 0, toString() { - const maxFps = getPref("video_max_fps"); + let maxFps = getPref("video_max_fps"); return maxFps < 60 ? `${maxFps}/${this.current}` : this.current.toString(); } }, @@ -1000,7 +994,7 @@ class StreamStatsCollector { received: 0, dropped: 0, toString() { - const framesDroppedPercentage = (this.dropped * 100 / (this.dropped + this.received || 1)).toFixed(2); + let framesDroppedPercentage = (this.dropped * 100 / (this.dropped + this.received || 1)).toFixed(2); return framesDroppedPercentage === "0.00" ? this.dropped.toString() : `${this.dropped} (${framesDroppedPercentage}%)`; } }, @@ -1008,7 +1002,7 @@ class StreamStatsCollector { received: 0, dropped: 0, toString() { - const packetsLostPercentage = (this.dropped * 100 / (this.dropped + this.received || 1)).toFixed(2); + let packetsLostPercentage = (this.dropped * 100 / (this.dropped + this.received || 1)).toFixed(2); return packetsLostPercentage === "0.00" ? this.dropped.toString() : `${this.dropped} (${packetsLostPercentage}%)`; } }, @@ -1046,7 +1040,7 @@ class StreamStatsCollector { toString() { let text = `${this.current}%`; if (this.current !== this.start) { - const diffLevel = Math.round(this.current - this.start), sign = diffLevel > 0 ? "+" : ""; + let diffLevel = Math.round(this.current - this.start), sign = diffLevel > 0 ? "+" : ""; text += ` (${sign}${diffLevel}%)`; } return text; @@ -1064,51 +1058,51 @@ class StreamStatsCollector { }; lastVideoStat; async collect() { - const stats = await STATES.currentStream.peerConnection?.getStats(); + let stats = await STATES.currentStream.peerConnection?.getStats(); if (!stats) return; stats.forEach((stat) => { if (stat.type === "inbound-rtp" && stat.kind === "video") { - const fps = this.currentStats.fps; + let fps = this.currentStats.fps; fps.current = stat.framesPerSecond || 0; - const pl = this.currentStats.pl; + let pl = this.currentStats.pl; pl.dropped = Math.max(0, stat.packetsLost), pl.received = stat.packetsReceived; - const fl = this.currentStats.fl; + let fl = this.currentStats.fl; if (fl.dropped = stat.framesDropped, fl.received = stat.framesReceived, !this.lastVideoStat) { this.lastVideoStat = stat; return; } - const lastStat = this.lastVideoStat, jit = this.currentStats.jit, bufferDelayDiff = stat.jitterBufferDelay - lastStat.jitterBufferDelay, emittedCountDiff = stat.jitterBufferEmittedCount - lastStat.jitterBufferEmittedCount; + let lastStat = this.lastVideoStat, jit = this.currentStats.jit, bufferDelayDiff = stat.jitterBufferDelay - lastStat.jitterBufferDelay, emittedCountDiff = stat.jitterBufferEmittedCount - lastStat.jitterBufferEmittedCount; if (emittedCountDiff > 0) jit.current = bufferDelayDiff / emittedCountDiff * 1000; - const btr = this.currentStats.btr, timeDiff = stat.timestamp - lastStat.timestamp; + let btr = this.currentStats.btr, timeDiff = stat.timestamp - lastStat.timestamp; btr.current = 8 * (stat.bytesReceived - lastStat.bytesReceived) / timeDiff / 1000; - const dt = this.currentStats.dt; + let dt = this.currentStats.dt; dt.total = stat.totalDecodeTime - lastStat.totalDecodeTime; - const framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded; + let framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded; dt.current = dt.total / framesDecodedDiff * 1000, this.lastVideoStat = stat; } else if (stat.type === "candidate-pair" && stat.packetsReceived > 0 && stat.state === "succeeded") { - const ping = this.currentStats.ping; + let ping = this.currentStats.ping; ping.current = stat.currentRoundTripTime ? stat.currentRoundTripTime * 1000 : -1; - const dl = this.currentStats.dl; + let dl = this.currentStats.dl; dl.total = stat.bytesReceived; - const ul = this.currentStats.ul; + let ul = this.currentStats.ul; ul.total = stat.bytesSent; } }); let batteryLevel = 100, isCharging = !1; if (STATES.browser.capabilities.batteryApi) try { - const bm = await navigator.getBattery(); + let bm = await navigator.getBattery(); isCharging = bm.charging, batteryLevel = Math.round(bm.level * 100); } catch (e) {} - const battery = this.currentStats.batt; + let battery = this.currentStats.batt; battery.current = batteryLevel, battery.isCharging = isCharging; - const playTime = this.currentStats.play, now = +new Date; + let playTime = this.currentStats.play, now = +new Date; playTime.seconds = Math.ceil((now - playTime.startTime) / 1000); } getStat(kind) { return this.currentStats[kind]; } reset() { - const playTime = this.currentStats.play; + let playTime = this.currentStats.play; playTime.seconds = 0, playTime.startTime = +new Date; try { STATES.browser.capabilities.batteryApi && navigator.getBattery().then((bm) => { @@ -1123,15 +1117,14 @@ class StreamStatsCollector { } } function getSupportedCodecProfiles() { - const options = { + let options = { default: t("default") }; if (!("getCapabilities" in RTCRtpReceiver)) return options; - let hasLowCodec = !1, hasNormalCodec = !1, hasHighCodec = !1; - const codecs = RTCRtpReceiver.getCapabilities("video").codecs; + let hasLowCodec = !1, hasNormalCodec = !1, hasHighCodec = !1, codecs = RTCRtpReceiver.getCapabilities("video").codecs; for (let codec of codecs) { if (codec.mimeType.toLowerCase() !== "video/h264" || !codec.sdpFmtpLine) continue; - const fmtp = codec.sdpFmtpLine.toLowerCase(); + let fmtp = codec.sdpFmtpLine.toLowerCase(); if (fmtp.includes("profile-level-id=4d")) hasHighCodec = !0; else if (fmtp.includes("profile-level-id=42e")) hasNormalCodec = !0; else if (fmtp.includes("profile-level-id=420")) hasLowCodec = !0; @@ -1225,7 +1218,7 @@ class GlobalSettingsStorage extends BaseSettingsStore { default: "default", options: getSupportedCodecProfiles(), ready: (setting) => { - const options = setting.options, keys = Object.keys(options); + let options = setting.options, keys = Object.keys(options); if (keys.length <= 1) setting.unsupported = !0, setting.unsupportedNote = "⚠️ " + t("browser-unsupported-feature"); setting.suggest = { lowest: keys.length === 1 ? keys[0] : keys[1], @@ -1715,7 +1708,7 @@ class GlobalSettingsStorage extends BaseSettingsStore { size: 6 }, ready: (setting) => { - const multipleOptions = setting.multipleOptions; + let multipleOptions = setting.multipleOptions; if (!STATES.browser.capabilities.batteryApi) delete multipleOptions["batt"]; } }, @@ -1808,7 +1801,7 @@ class Screenshot { }); } static updateCanvasSize(width, height) { - const $canvas = Screenshot.#$canvas; + let $canvas = Screenshot.#$canvas; if ($canvas) $canvas.width = width, $canvas.height = height; } static updateCanvasFilters(filters) { @@ -1818,22 +1811,22 @@ class Screenshot { e.target.classList.remove("bx-taking-screenshot"); } static takeScreenshot(callback) { - const currentStream = STATES.currentStream, streamPlayer = currentStream.streamPlayer, $canvas = Screenshot.#$canvas; + let currentStream = STATES.currentStream, streamPlayer = currentStream.streamPlayer, $canvas = Screenshot.#$canvas; if (!streamPlayer || !$canvas) return; let $player; if (getPref("screenshot_apply_filters")) $player = streamPlayer.getPlayerElement(); else $player = streamPlayer.getPlayerElement("default"); if (!$player || !$player.isConnected) return; $player.parentElement.addEventListener("animationend", this.#onAnimationEnd, { once: !0 }), $player.parentElement.classList.add("bx-taking-screenshot"); - const canvasContext = Screenshot.#canvasContext; + let canvasContext = Screenshot.#canvasContext; if ($player instanceof HTMLCanvasElement) streamPlayer.getWebGL2Player().drawFrame(); if (canvasContext.drawImage($player, 0, 0, $canvas.width, $canvas.height), AppInterface) { - const data = $canvas.toDataURL("image/png").split(";base64,")[1]; + let data = $canvas.toDataURL("image/png").split(";base64,")[1]; AppInterface.saveScreenshot(currentStream.titleSlug, data), canvasContext.clearRect(0, 0, $canvas.width, $canvas.height), callback && callback(); return; } $canvas && $canvas.toBlob((blob) => { - const now = +new Date, $anchor = CE("a", { + let now = +new Date, $anchor = CE("a", { download: `${currentStream.titleSlug}-${now}.png`, href: URL.createObjectURL(blob) }); @@ -1953,11 +1946,11 @@ class StreamStats { isGlancing = () => this.$container.dataset.display === "glancing"; quickGlanceSetup() { if (!STATES.isPlaying || this.quickGlanceObserver) return; - const $uiContainer = document.querySelector("div[data-testid=ui-container]"); + let $uiContainer = document.querySelector("div[data-testid=ui-container]"); if (!$uiContainer) return; this.quickGlanceObserver = new MutationObserver((mutationList, observer) => { for (let record of mutationList) { - const $target = record.target; + let $target = record.target; if (!$target.className || !$target.className.startsWith("GripHandle")) continue; if (record.target.ariaExpanded === "true") this.isHidden() && this.start(!0); else this.stop(!0); @@ -1976,20 +1969,18 @@ class StreamStats { this.onStoppedPlaying(); return; } - const PREF_STATS_CONDITIONAL_FORMATTING = getPref("stats_conditional_formatting"); - let grade = ""; - const statsCollector = StreamStatsCollector.getInstance(); + let PREF_STATS_CONDITIONAL_FORMATTING = getPref("stats_conditional_formatting"), grade = "", statsCollector = StreamStatsCollector.getInstance(); await statsCollector.collect(); let statKey; for (statKey in this.stats) { grade = ""; - const stat = this.stats[statKey], value = statsCollector.getStat(statKey), $element = stat.$element; + let stat = this.stats[statKey], value = statsCollector.getStat(statKey), $element = stat.$element; if ($element.textContent = value.toString(), PREF_STATS_CONDITIONAL_FORMATTING && "grades" in value) grade = statsCollector.calculateGrade(value.current, value.grades); if ($element.dataset.grade !== grade) $element.dataset.grade = grade; } } refreshStyles() { - const PREF_ITEMS = getPref("stats_items"), $container = this.$container; + let PREF_ITEMS = getPref("stats_items"), $container = this.$container; $container.dataset.stats = "[" + PREF_ITEMS.join("][") + "]", $container.dataset.position = getPref("stats_position"), $container.dataset.transparent = getPref("stats_transparent"), $container.style.opacity = getPref("stats_opacity") + "%", $container.style.fontSize = getPref("stats_text_size"); } hideSettingsUi() { @@ -1999,7 +1990,7 @@ class StreamStats { this.$container = CE("div", { class: "bx-stats-bar bx-gone" }); let statKey; for (statKey in this.stats) { - const stat = this.stats[statKey], $div = CE("div", { + let stat = this.stats[statKey], $div = CE("div", { class: `bx-stat-${statKey}`, title: stat.name }, CE("label", {}, statKey.toUpperCase()), stat.$element); @@ -2009,7 +2000,7 @@ class StreamStats { } static setupEvents() { window.addEventListener(BxEvent.STREAM_PLAYING, (e) => { - const PREF_STATS_QUICK_GLANCE = getPref("stats_quick_glance"), PREF_STATS_SHOW_WHEN_PLAYING = getPref("stats_show_when_playing"), streamStats = StreamStats.getInstance(); + let PREF_STATS_QUICK_GLANCE = getPref("stats_quick_glance"), PREF_STATS_SHOW_WHEN_PLAYING = getPref("stats_show_when_playing"), streamStats = StreamStats.getInstance(); if (PREF_STATS_SHOW_WHEN_PLAYING) streamStats.start(); else if (PREF_STATS_QUICK_GLANCE) streamStats.quickGlanceSetup(), !PREF_STATS_SHOW_WHEN_PLAYING && streamStats.start(!0); }); @@ -2028,7 +2019,7 @@ class Toast { static DURATION = 3000; static show(msg, status, options = {}) { options = options || {}; - const args = Array.from(arguments); + let args = Array.from(arguments); if (options.instant) Toast.stack = [args], Toast.showNext(); else Toast.stack.push(args), !Toast.isShowing && Toast.showNext(); } @@ -2038,22 +2029,22 @@ class Toast { return; } Toast.isShowing = !0, Toast.timeout && clearTimeout(Toast.timeout), Toast.timeout = window.setTimeout(Toast.hide, Toast.DURATION); - const [msg, status, options] = Toast.stack.shift(); + let [msg, status, options] = Toast.stack.shift(); if (options && options.html) Toast.$msg.innerHTML = msg; else Toast.$msg.textContent = msg; if (status) Toast.$status.classList.remove("bx-gone"), Toast.$status.textContent = status; else Toast.$status.classList.add("bx-gone"); - const classList = Toast.$wrapper.classList; + let classList = Toast.$wrapper.classList; classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-show"); } static hide() { Toast.timeout = null; - const classList = Toast.$wrapper.classList; + let classList = Toast.$wrapper.classList; classList.remove("bx-show"), classList.add("bx-hide"); } static setup() { Toast.$wrapper = CE("div", { class: "bx-toast bx-offscreen" }, Toast.$msg = CE("span", { class: "bx-toast-msg" }), Toast.$status = CE("span", { class: "bx-toast-status" })), Toast.$wrapper.addEventListener("transitionend", (e) => { - const classList = Toast.$wrapper.classList; + let classList = Toast.$wrapper.classList; if (classList.contains("bx-hide")) classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-offscreen"), Toast.showNext(); }), document.documentElement.appendChild(Toast.$wrapper); } @@ -2061,7 +2052,7 @@ class Toast { class MicrophoneShortcut { static toggle(showToast = !0) { if (!window.BX_EXPOSED.streamSession) return !1; - const enableMic = window.BX_EXPOSED.streamSession._microphoneState === "Enabled" ? !1 : !0; + let enableMic = window.BX_EXPOSED.streamSession._microphoneState === "Enabled" ? !1 : !0; try { return window.BX_EXPOSED.streamSession.tryEnableChatAsync(enableMic), showToast && Toast.show(t("microphone"), t(enableMic ? "unmuted" : "muted"), { instant: !0 }), enableMic; } catch (e) { @@ -2077,7 +2068,7 @@ class StreamUiShortcut { } function checkForUpdate() { if (SCRIPT_VERSION.includes("beta")) return; - const CHECK_INTERVAL_SECONDS = 7200, currentVersion = getPref("version_current"), lastCheck = getPref("version_last_check"), now = Math.round(+new Date / 1000); + let CHECK_INTERVAL_SECONDS = 7200, currentVersion = getPref("version_current"), lastCheck = getPref("version_last_check"), now = Math.round(+new Date / 1000); if (currentVersion === SCRIPT_VERSION && now - lastCheck < CHECK_INTERVAL_SECONDS) return; setPref("version_last_check", now), fetch("https://api.github.com/repos/redphx/better-xcloud/releases/latest").then((response) => response.json()).then((json) => { setPref("version_latest", json.tag_name.substring(1)), setPref("version_current", SCRIPT_VERSION); @@ -2092,14 +2083,14 @@ function disablePwa() { function hashCode(str) { let hash = 0; for (let i = 0, len = str.length;i < len; i++) { - const chr = str.charCodeAt(i); + let chr = str.charCodeAt(i); hash = (hash << 5) - hash + chr, hash |= 0; } return hash; } function renderString(str, obj) { return str.replace(/\$\{.+?\}/g, (match) => { - const key = match.substring(2, match.length - 1); + let key = match.substring(2, match.length - 1); if (key in obj) return obj[key]; return match; }); @@ -2124,8 +2115,7 @@ function productTitleToSlug(title) { class SoundShortcut { static adjustGainNodeVolume(amount) { if (!getPref("audio_enable_volume_control")) return 0; - const currentValue = getPref("audio_volume"); - let nearestValue; + let currentValue = getPref("audio_volume"), nearestValue; if (amount > 0) nearestValue = ceilToNearest(currentValue, amount); else nearestValue = floorToNearest(currentValue, -1 * amount); let newValue; @@ -2138,8 +2128,7 @@ class SoundShortcut { } static muteUnmute() { if (getPref("audio_enable_volume_control") && STATES.currentStream.audioGainNode) { - const gainValue = STATES.currentStream.audioGainNode.gain.value, settingValue = getPref("audio_volume"); - let targetValue; + let gainValue = STATES.currentStream.audioGainNode.gain.value, settingValue = getPref("audio_volume"), targetValue; if (settingValue === 0) targetValue = 100, setPref("audio_volume", targetValue, !0); else if (gainValue === 0) targetValue = settingValue; else targetValue = 0; @@ -2155,7 +2144,7 @@ class SoundShortcut { if ($media = document.querySelector("div[data-testid=media-container] audio"), !$media) $media = document.querySelector("div[data-testid=media-container] video"); if ($media) { $media.muted = !$media.muted; - const status = $media.muted ? t("muted") : t("unmuted"); + let status = $media.muted ? t("muted") : t("unmuted"); Toast.show(`${t("stream")} ❯ ${t("volume")}`, status, { instant: !0 }), BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, { speakerState: $media.muted ? 1 : 0 }); @@ -2165,46 +2154,43 @@ class SoundShortcut { class BxSelectElement { static wrap($select) { $select.removeAttribute("tabindex"); - const $btnPrev = createButton({ + let $btnPrev = createButton({ label: "<", style: 32 }), $btnNext = createButton({ label: ">", style: 32 - }), isMultiple = $select.multiple; - let $checkBox, $label, visibleIndex = $select.selectedIndex, $content; + }), isMultiple = $select.multiple, $checkBox, $label, visibleIndex = $select.selectedIndex, $content; if (isMultiple) $content = CE("button", { class: "bx-select-value bx-focusable", tabindex: 0 }, $checkBox = CE("input", { type: "checkbox" }), $label = CE("span", {}, "")), $content.addEventListener("click", (e) => { $checkBox.click(); }), $checkBox.addEventListener("input", (e) => { - const $option = getOptionAtIndex(visibleIndex); + let $option = getOptionAtIndex(visibleIndex); $option && ($option.selected = e.target.checked), BxEvent.dispatch($select, "input"); }); else $content = CE("div", {}, $label = CE("label", { for: $select.id + "_checkbox" }, "")); - const getOptionAtIndex = (index) => { + let getOptionAtIndex = (index) => { return Array.from($select.querySelectorAll("option"))[index]; }, render = (e) => { if (e && e.manualTrigger) visibleIndex = $select.selectedIndex; visibleIndex = normalizeIndex(visibleIndex); - const $option = getOptionAtIndex(visibleIndex); - let content = ""; + let $option = getOptionAtIndex(visibleIndex), content = ""; if ($option) if (content = $option.textContent || "", content && $option.parentElement.tagName === "OPTGROUP") { $label.innerHTML = ""; - const fragment = document.createDocumentFragment(); + let fragment = document.createDocumentFragment(); fragment.appendChild(CE("span", {}, $option.parentElement.label)), fragment.appendChild(document.createTextNode(content)), $label.appendChild(fragment); } else $label.textContent = content; else $label.textContent = content; if ($label.classList.toggle("bx-line-through", $option && $option.disabled), isMultiple) $checkBox.checked = $option?.selected || !1, $checkBox.classList.toggle("bx-gone", !content); - const disablePrev = visibleIndex <= 0, disableNext = visibleIndex === $select.querySelectorAll("option").length - 1; + let disablePrev = visibleIndex <= 0, disableNext = visibleIndex === $select.querySelectorAll("option").length - 1; $btnPrev.classList.toggle("bx-inactive", disablePrev), $btnNext.classList.toggle("bx-inactive", disableNext), disablePrev && !disableNext && document.activeElement === $btnPrev && $btnNext.focus(), disableNext && !disablePrev && document.activeElement === $btnNext && $btnPrev.focus(); }, normalizeIndex = (index) => { return Math.min(Math.max(index, 0), $select.querySelectorAll("option").length - 1); }, onPrevNext = (e) => { if (!e.target) return; - const goNext = e.target.closest("button") === $btnNext, currentIndex = visibleIndex; - let newIndex = goNext ? currentIndex + 1 : currentIndex - 1; + let goNext = e.target.closest("button") === $btnNext, currentIndex = visibleIndex, newIndex = goNext ? currentIndex + 1 : currentIndex - 1; if (newIndex = normalizeIndex(newIndex), visibleIndex = newIndex, !isMultiple && newIndex !== currentIndex) $select.selectedIndex = newIndex; if (isMultiple) render(); else BxEvent.dispatch($select, "input"); @@ -2218,7 +2204,7 @@ class BxSelectElement { childList: !0, attributes: !0 }), render(); - const $div = CE("div", { + let $div = CE("div", { class: "bx-select", _nearby: { orientation: "horizontal", @@ -2245,10 +2231,9 @@ class BxSelectElement { } } function onChangeVideoPlayerType() { - const playerType = getPref("video_player_type"), $videoProcessing = document.getElementById(`bx_setting_${"video_processing"}`), $videoSharpness = document.getElementById(`bx_setting_${"video_sharpness"}`), $videoPowerPreference = document.getElementById(`bx_setting_${"video_power_preference"}`), $videoMaxFps = document.getElementById(`bx_setting_${"video_max_fps"}`); + let playerType = getPref("video_player_type"), $videoProcessing = document.getElementById(`bx_setting_${"video_processing"}`), $videoSharpness = document.getElementById(`bx_setting_${"video_sharpness"}`), $videoPowerPreference = document.getElementById(`bx_setting_${"video_power_preference"}`), $videoMaxFps = document.getElementById(`bx_setting_${"video_max_fps"}`); if (!$videoProcessing) return; - let isDisabled = !1; - const $optCas = $videoProcessing.querySelector(`option[value=${"cas"}]`); + let isDisabled = !1, $optCas = $videoProcessing.querySelector(`option[value=${"cas"}]`); if (playerType === "webgl2") $optCas && ($optCas.disabled = !1); else if ($videoProcessing.value = "usm", setPref("video_processing", "usm"), $optCas && ($optCas.disabled = !0), UserAgent.isSafari()) isDisabled = !0; $videoProcessing.disabled = isDisabled, $videoSharpness.dataset.disabled = isDisabled.toString(), $videoPowerPreference.closest(".bx-settings-row").classList.toggle("bx-gone", playerType !== "webgl2"), $videoMaxFps.closest(".bx-settings-row").classList.toggle("bx-gone", playerType !== "webgl2"), updateVideoPlayer(); @@ -2257,10 +2242,10 @@ function limitVideoPlayerFps(targetFps) { STATES.currentStream.streamPlayer?.getWebGL2Player()?.setTargetFps(targetFps); } function updateVideoPlayer() { - const streamPlayer = STATES.currentStream.streamPlayer; + let streamPlayer = STATES.currentStream.streamPlayer; if (!streamPlayer) return; limitVideoPlayerFps(getPref("video_max_fps")); - const options = { + let options = { processing: getPref("video_processing"), sharpness: getPref("video_sharpness"), saturation: getPref("video_saturation"), @@ -2352,16 +2337,16 @@ class MkbPreset { } }; static convert(preset) { - const obj = { + let obj = { mapping: {}, mouse: Object.assign({}, preset.mouse) }; for (let buttonIndex in preset.mapping) for (let keyName of preset.mapping[parseInt(buttonIndex)]) obj.mapping[keyName] = parseInt(buttonIndex); - const mouse = obj.mouse; + let mouse = obj.mouse; mouse["sensitivity_x"] *= EmulatedMkbHandler.DEFAULT_PANNING_SENSITIVITY, mouse["sensitivity_y"] *= EmulatedMkbHandler.DEFAULT_PANNING_SENSITIVITY, mouse["deadzone_counterweight"] *= EmulatedMkbHandler.DEFAULT_DEADZONE_COUNTERWEIGHT; - const mouseMapTo = MouseMapTo[mouse["map_to"]]; + let mouseMapTo = MouseMapTo[mouse["map_to"]]; if (typeof mouseMapTo !== "undefined") mouse["map_to"] = mouseMapTo; else mouse["map_to"] = MkbPreset.MOUSE_SETTINGS["map_to"].default; return console.log(obj), obj; @@ -2383,9 +2368,9 @@ class LocalDb { resolve(); return; } - const request = window.indexedDB.open(LocalDb.DB_NAME, LocalDb.DB_VERSION); + let request = window.indexedDB.open(LocalDb.DB_NAME, LocalDb.DB_VERSION); request.onupgradeneeded = (e) => { - const db = e.target.result; + let db = e.target.result; switch (e.oldVersion) { case 0: { db.createObjectStore(LocalDb.TABLE_PRESETS, { keyPath: "id", autoIncrement: !0 }).createIndex("name_idx", "name"); @@ -2400,13 +2385,13 @@ class LocalDb { }); } #table(name, type) { - const table = this.#DB.transaction(name, type || "readonly").objectStore(name); + let table = this.#DB.transaction(name, type || "readonly").objectStore(name); return new Promise((resolve) => resolve(table)); } #call(method) { - const table = arguments[1]; + let table = arguments[1]; return new Promise((resolve) => { - const request = method.call(table, ...Array.from(arguments).slice(2)); + let request = method.call(table, ...Array.from(arguments).slice(2)); request.onsuccess = (e) => { resolve([table, e.target.result]); }; @@ -2446,11 +2431,11 @@ class LocalDb { return this.#open().then(() => this.#table(LocalDb.TABLE_PRESETS, "readwrite")).then((table) => this.#count(table)).then(([table, count]) => { if (count > 0) return new Promise((resolve) => { this.#getAll(table).then(([table2, items]) => { - const presets = {}; + let presets = {}; items.forEach((item2) => presets[item2.id] = item2), resolve(presets); }); }); - const preset = { + let preset = { name: t("default"), data: MkbPreset.DEFAULT_PRESET }; @@ -2504,8 +2489,7 @@ class PointerClient { }), this.socket.addEventListener("close", (event) => { this.socket = null; }), this.socket.addEventListener("message", (event) => { - const dataView = new DataView(event.data); - let messageType = dataView.getInt8(0), offset = Int8Array.BYTES_PER_ELEMENT; + let dataView = new DataView(event.data), messageType = dataView.getInt8(0), offset = Int8Array.BYTES_PER_ELEMENT; switch (messageType) { case 1: this.onMove(dataView, offset); @@ -2523,25 +2507,25 @@ class PointerClient { }); } onMove(dataView, offset) { - const x = dataView.getInt16(offset); + let x = dataView.getInt16(offset); offset += Int16Array.BYTES_PER_ELEMENT; - const y = dataView.getInt16(offset); + let y = dataView.getInt16(offset); this.mkbHandler?.handleMouseMove({ movementX: x, movementY: y }); } onPress(messageType, dataView, offset) { - const button = dataView.getUint8(offset); + let button = dataView.getUint8(offset); this.mkbHandler?.handleMouseClick({ pointerButton: button, pressed: messageType === 2 }); } onScroll(dataView, offset) { - const vScroll = dataView.getInt16(offset); + let vScroll = dataView.getInt16(offset); offset += Int16Array.BYTES_PER_ELEMENT; - const hScroll = dataView.getInt16(offset); + let hScroll = dataView.getInt16(offset); this.mkbHandler?.handleMouseWheel({ vertical: vScroll, horizontal: hScroll @@ -2675,7 +2659,7 @@ class NativeMkbHandler extends MkbHandler { }); } handleMouseClick(data) { - const { pointerButton, pressed } = data; + let { pointerButton, pressed } = data; if (pressed) this.#mouseButtonsPressed |= pointerButton; else this.#mouseButtonsPressed ^= pointerButton; this.#mouseButtonsPressed = Math.max(0, this.#mouseButtonsPressed), this.#sendMouseInput({ @@ -2687,7 +2671,7 @@ class NativeMkbHandler extends MkbHandler { }); } handleMouseWheel(data) { - const { vertical, horizontal } = data; + let { vertical, horizontal } = data; if (this.#mouseWheelX = horizontal, this.#mouseHorizontalMultiply && this.#mouseHorizontalMultiply !== 1) this.#mouseWheelX *= this.#mouseHorizontalMultiply; if (this.#mouseWheelY = vertical, this.#mouseVerticalMultiply && this.#mouseVerticalMultiply !== 1) this.#mouseWheelY *= this.#mouseVerticalMultiply; return this.#sendMouseInput({ @@ -2764,7 +2748,7 @@ class PointerLockMouseDataProvider extends MouseDataProvider { }; #onMouseEvent = (e) => { e.preventDefault(); - const isMouseDown = e.type === "mousedown", data = { + let isMouseDown = e.type === "mousedown", data = { mouseButton: e.button, pressed: isMouseDown }; @@ -2772,7 +2756,7 @@ class PointerLockMouseDataProvider extends MouseDataProvider { }; #onWheelEvent = (e) => { if (!KeyHelper.getKeyFromEvent(e)) return; - const data = { + let data = { vertical: e.deltaY, horizontal: e.deltaX }; @@ -2827,24 +2811,24 @@ class EmulatedMkbHandler extends MkbHandler { } isEnabled = () => this.#enabled; #patchedGetGamepads = () => { - const gamepads = this.#nativeGetGamepads() || []; + let gamepads = this.#nativeGetGamepads() || []; return gamepads[this.#VIRTUAL_GAMEPAD.index] = this.#VIRTUAL_GAMEPAD, gamepads; }; #getVirtualGamepad = () => this.#VIRTUAL_GAMEPAD; #updateStick(stick, x, y) { - const virtualGamepad = this.#getVirtualGamepad(); + let virtualGamepad = this.#getVirtualGamepad(); virtualGamepad.axes[stick * 2] = x, virtualGamepad.axes[stick * 2 + 1] = y, virtualGamepad.timestamp = performance.now(); } #vectorLength = (x, y) => Math.sqrt(x ** 2 + y ** 2); #resetGamepad = () => { - const gamepad = this.#getVirtualGamepad(); + let gamepad = this.#getVirtualGamepad(); gamepad.axes = [0, 0, 0, 0]; for (let button of gamepad.buttons) button.pressed = !1, button.value = 0; gamepad.timestamp = performance.now(); }; #pressButton = (buttonIndex, pressed) => { - const virtualGamepad = this.#getVirtualGamepad(); + let virtualGamepad = this.#getVirtualGamepad(); if (buttonIndex >= 100) { let [valueArr, axisIndex] = this.#STICK_MAP[buttonIndex]; valueArr = valueArr, axisIndex = axisIndex; @@ -2859,7 +2843,7 @@ class EmulatedMkbHandler extends MkbHandler { virtualGamepad.timestamp = performance.now(); }; #onKeyboardEvent = (e) => { - const isKeyDown = e.type === "keydown"; + let isKeyDown = e.type === "keydown"; if (e.code === "F8") { if (!isKeyDown) e.preventDefault(), this.toggle(); return; @@ -2872,38 +2856,37 @@ class EmulatedMkbHandler extends MkbHandler { return; } if (!this.#isPolling) return; - const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[e.code || e.key]; + let buttonIndex = this.#CURRENT_PRESET_DATA.mapping[e.code || e.key]; if (typeof buttonIndex === "undefined") return; if (e.repeat) return; e.preventDefault(), this.#pressButton(buttonIndex, isKeyDown); }; #onMouseStopped = () => { this.#detectMouseStoppedTimeout = null; - const analog = this.#CURRENT_PRESET_DATA.mouse["map_to"] === 1 ? 0 : 1; + let analog = this.#CURRENT_PRESET_DATA.mouse["map_to"] === 1 ? 0 : 1; this.#updateStick(analog, 0, 0); }; handleMouseClick = (data) => { let mouseButton; if (typeof data.mouseButton !== "undefined") mouseButton = data.mouseButton; else if (typeof data.pointerButton !== "undefined") mouseButton = PointerToMouseButton[data.pointerButton]; - const keyCode = "Mouse" + mouseButton, key = { + let keyCode = "Mouse" + mouseButton, key = { code: keyCode, name: KeyHelper.codeToKeyName(keyCode) }; if (!key.name) return; - const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code]; + let buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code]; if (typeof buttonIndex === "undefined") return; this.#pressButton(buttonIndex, data.pressed); }; handleMouseMove = (data) => { - const mouseMapTo = this.#CURRENT_PRESET_DATA.mouse["map_to"]; + let mouseMapTo = this.#CURRENT_PRESET_DATA.mouse["map_to"]; if (mouseMapTo === 0) return; this.#detectMouseStoppedTimeout && clearTimeout(this.#detectMouseStoppedTimeout), this.#detectMouseStoppedTimeout = window.setTimeout(this.#onMouseStopped.bind(this), 50); - const deadzoneCounterweight = this.#CURRENT_PRESET_DATA.mouse["deadzone_counterweight"]; - let x = data.movementX * this.#CURRENT_PRESET_DATA.mouse["sensitivity_x"], y = data.movementY * this.#CURRENT_PRESET_DATA.mouse["sensitivity_y"], length = this.#vectorLength(x, y); + let deadzoneCounterweight = this.#CURRENT_PRESET_DATA.mouse["deadzone_counterweight"], x = data.movementX * this.#CURRENT_PRESET_DATA.mouse["sensitivity_x"], y = data.movementY * this.#CURRENT_PRESET_DATA.mouse["sensitivity_y"], length = this.#vectorLength(x, y); if (length !== 0 && length < deadzoneCounterweight) x *= deadzoneCounterweight / length, y *= deadzoneCounterweight / length; else if (length > EmulatedMkbHandler.MAXIMUM_STICK_RANGE) x *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length, y *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length; - const analog = mouseMapTo === 1 ? 0 : 1; + let analog = mouseMapTo === 1 ? 0 : 1; this.#updateStick(analog, x, y); }; handleMouseWheel = (data) => { @@ -2913,7 +2896,7 @@ class EmulatedMkbHandler extends MkbHandler { else if (data.horizontal < 0) code = "ScrollLeft"; else if (data.horizontal > 0) code = "ScrollRight"; if (!code) return !1; - const key = { + let key = { code, name: KeyHelper.codeToKeyName(code) }, buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code]; @@ -2931,7 +2914,7 @@ class EmulatedMkbHandler extends MkbHandler { }; #getCurrentPreset = () => { return new Promise((resolve) => { - const presetId = getPref("mkb_default_preset_id"); + let presetId = getPref("mkb_default_preset_id"); LocalDb.INSTANCE.getPreset(presetId).then((preset) => { resolve(preset); }); @@ -2970,7 +2953,7 @@ class EmulatedMkbHandler extends MkbHandler { label: t("edit"), onClick: (e) => { e.preventDefault(), e.stopPropagation(); - const dialog = SettingsNavigationDialog.getInstance(); + let dialog = SettingsNavigationDialog.getInstance(); dialog.focusTab("mkb"), NavigationDialogManager.getInstance().show(dialog); } })))); @@ -3015,14 +2998,14 @@ class EmulatedMkbHandler extends MkbHandler { start = () => { if (!this.#enabled) this.#enabled = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 }); this.#isPolling = !0, this.#escKeyDownTime = -1, this.#resetGamepad(), window.navigator.getGamepads = this.#patchedGetGamepads, this.waitForMouseData(!1), this.#mouseDataProvider?.start(); - const virtualGamepad = this.#getVirtualGamepad(); + let virtualGamepad = this.#getVirtualGamepad(); virtualGamepad.connected = !0, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepadconnected", { gamepad: virtualGamepad }), window.BX_EXPOSED.stopTakRendering = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 }); }; stop = () => { this.#enabled = !1, this.#isPolling = !1, this.#escKeyDownTime = -1; - const virtualGamepad = this.#getVirtualGamepad(); + let virtualGamepad = this.#getVirtualGamepad(); if (virtualGamepad.connected) this.#resetGamepad(), virtualGamepad.connected = !1, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepaddisconnected", { gamepad: virtualGamepad }), window.navigator.getGamepads = this.#nativeGetGamepads; @@ -3048,7 +3031,7 @@ class NavigationDialog { NavigationDialogManager.getInstance().hide(); } getFocusedElement() { - const $activeElement = document.activeElement; + let $activeElement = document.activeElement; if (!$activeElement) return null; if (this.$container.contains($activeElement)) return $activeElement; return null; @@ -3112,20 +3095,19 @@ class NavigationDialogManager { }), document.documentElement.appendChild(this.$overlay), this.$container = CE("div", { class: "bx-navigation-dialog bx-gone" }), document.documentElement.appendChild(this.$container), window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, (e) => this.hide()), getPref("ui_controller_friendly")) new MutationObserver((mutationList) => { if (mutationList.length === 0 || mutationList[0].addedNodes.length === 0) return; - const $dialog = mutationList[0].addedNodes[0]; + let $dialog = mutationList[0].addedNodes[0]; if (!$dialog || !($dialog instanceof HTMLElement)) return; this.calculateSelectBoxes($dialog); }).observe(this.$container, { childList: !0 }); } calculateSelectBoxes($root) { $root.querySelectorAll(".bx-select:not([data-calculated]) select").forEach(($select) => { - const $parent = $select.parentElement; + let $parent = $select.parentElement; if ($parent.classList.contains("bx-full-width")) { $parent.dataset.calculated = "true"; return; } - const rect = $select.getBoundingClientRect(); - let $label, width = Math.ceil(rect.width); + let rect = $select.getBoundingClientRect(), $label, width = Math.ceil(rect.width); if (!width) return; if ($select.multiple) $label = $parent.querySelector(".bx-select-value"), width += 20; else $label = $parent.querySelector("div"); @@ -3135,8 +3117,7 @@ class NavigationDialogManager { handleEvent(event) { switch (event.type) { case "keydown": - const $target = event.target, keyboardEvent = event, keyCode = keyboardEvent.code || keyboardEvent.key; - let handled = this.dialog?.handleKeyPress(keyCode); + let $target = event.target, keyboardEvent = event, keyCode = keyboardEvent.code || keyboardEvent.key, handled = this.dialog?.handleKeyPress(keyCode); if (handled) { event.preventDefault(), event.stopPropagation(); return; @@ -3155,12 +3136,11 @@ class NavigationDialogManager { return this.$container && !this.$container.classList.contains("bx-gone"); } pollGamepad() { - const gamepads = window.navigator.getGamepads(); + let gamepads = window.navigator.getGamepads(); for (let gamepad of gamepads) { if (!gamepad || !gamepad.connected) continue; if (gamepad.id === VIRTUAL_GAMEPAD_ID) continue; - const { axes, buttons } = gamepad; - let releasedButton = null, heldButton = null, lastState = this.gamepadLastStates[gamepad.index], lastTimestamp, lastKey, lastKeyPressed; + let { axes, buttons } = gamepad, releasedButton = null, heldButton = null, lastState = this.gamepadLastStates[gamepad.index], lastTimestamp, lastKey, lastKeyPressed; if (lastState) [lastTimestamp, lastKey, lastKeyPressed] = lastState; if (lastTimestamp && lastTimestamp === gamepad.timestamp) continue; for (let key of NavigationDialogManager.GAMEPAD_KEYS) @@ -3173,7 +3153,7 @@ class NavigationDialogManager { } if (heldButton === null && releasedButton === null && axes && axes.length >= 2) { if (lastKey) { - const releasedHorizontal = Math.abs(axes[0]) < 0.1 && (lastKey === 102 || lastKey === 103), releasedVertical = Math.abs(axes[1]) < 0.1 && (lastKey === 100 || lastKey === 101); + let releasedHorizontal = Math.abs(axes[0]) < 0.1 && (lastKey === 102 || lastKey === 103), releasedVertical = Math.abs(axes[1]) < 0.1 && (lastKey === 100 || lastKey === 101); if (releasedHorizontal || releasedVertical) releasedButton = lastKey; else heldButton = lastKey; } else if (axes[0] < -0.5) heldButton = 102; @@ -3183,7 +3163,7 @@ class NavigationDialogManager { } if (heldButton !== null) { if (this.gamepadLastStates[gamepad.index] = [gamepad.timestamp, heldButton, !1], this.clearGamepadHoldingInterval(), NavigationDialogManager.GAMEPAD_DIRECTION_MAP[heldButton]) this.gamepadHoldingIntervalId = window.setInterval(() => { - const lastState2 = this.gamepadLastStates[gamepad.index]; + let lastState2 = this.gamepadLastStates[gamepad.index]; if (lastState2) { if ([lastTimestamp, lastKey, lastKeyPressed] = lastState2, lastKey === heldButton) { this.handleGamepad(gamepad, heldButton); @@ -3215,7 +3195,7 @@ class NavigationDialogManager { let direction = NavigationDialogManager.GAMEPAD_DIRECTION_MAP[key]; if (!direction) return !1; if (document.activeElement instanceof HTMLInputElement && document.activeElement.type === "range") { - const $range = document.activeElement; + let $range = document.activeElement; if (direction === 4 || direction === 2) $range.value = (parseInt($range.value) + parseInt($range.step) * (direction === 4 ? -1 : 1)).toString(), $range.dispatchEvent(new InputEvent("input")), handled = !0; } if (!handled) this.focusDirection(direction); @@ -3238,11 +3218,11 @@ class NavigationDialogManager { return $elm.focus(), $elm === document.activeElement; } getOrientation($elm) { - const nearby = $elm.nearby || {}; + let nearby = $elm.nearby || {}; if (nearby.selfOrientation) return nearby.selfOrientation; let orientation, $current = $elm.parentElement; while ($current !== this.$container) { - const tmp = $current.nearby?.orientation; + let tmp = $current.nearby?.orientation; if ($current.nearby && tmp) { orientation = tmp; break; @@ -3257,14 +3237,12 @@ class NavigationDialogManager { if (!$focusing || $focusing === this.$container) return null; if (checked.includes($focusing)) return null; checked.push($focusing); - let $target = $focusing; - const $parent = $target.parentElement, nearby = $target.nearby || {}, orientation = this.getOrientation($target); - let siblingProperty = NavigationDialogManager.SIBLING_PROPERTY_MAP[orientation][direction]; + let $target = $focusing, $parent = $target.parentElement, nearby = $target.nearby || {}, orientation = this.getOrientation($target), siblingProperty = NavigationDialogManager.SIBLING_PROPERTY_MAP[orientation][direction]; if (siblingProperty) { let $sibling = $target; while ($sibling[siblingProperty]) { $sibling = $sibling[siblingProperty]; - const $focusable = this.findFocusableElement($sibling, direction); + let $focusable = this.findFocusableElement($sibling, direction); if ($focusable) return $focusable; } } @@ -3279,18 +3257,18 @@ class NavigationDialogManager { if (!!$elm.disabled) return null; if (!isElementVisible($elm)) return null; if ($elm.tabIndex > -1) return $elm; - const focus = $elm.nearby?.focus; + let focus = $elm.nearby?.focus; if (focus) { if (focus instanceof HTMLElement) return this.findFocusableElement(focus, direction); else if (typeof focus === "function") { if (focus()) return document.activeElement; } } - const children = Array.from($elm.children), orientation = $elm.nearby?.orientation || "vertical"; + let children = Array.from($elm.children), orientation = $elm.nearby?.orientation || "vertical"; if (orientation === "horizontal" || orientation === "vertical" && direction === 1) children.reverse(); for (let $child of children) { if (!$child || !($child instanceof HTMLElement)) return null; - const $target = this.findFocusableElement($child, direction); + let $target = this.findFocusableElement($child, direction); if ($target) return $target; } return null; @@ -3302,15 +3280,15 @@ class NavigationDialogManager { this.gamepadLastStates = [], this.gamepadPollingIntervalId && window.clearInterval(this.gamepadPollingIntervalId), this.gamepadPollingIntervalId = null; } focusDirection(direction) { - const dialog = this.dialog; + let dialog = this.dialog; if (!dialog) return; - const $focusing = dialog.getFocusedElement(); + let $focusing = dialog.getFocusedElement(); if (!$focusing || !this.findFocusableElement($focusing, direction)) return dialog.focusIfNeeded(), null; - const $target = this.findNextTarget($focusing, direction, !0); + let $target = this.findNextTarget($focusing, direction, !0); this.focus($target); } unmountCurrentDialog() { - const dialog = this.dialog; + let dialog = this.dialog; dialog && dialog.onBeforeUnmount(), this.$container.firstChild?.remove(), dialog && dialog.onUnmounted(), this.dialog = null; } } @@ -3359,7 +3337,7 @@ class Dialog { $overlay; onClose; constructor(options) { - const { + let { title, className, content, @@ -3449,14 +3427,14 @@ class MkbRemapper { window.removeEventListener("keydown", this.#onKeyDown), window.removeEventListener("mousedown", this.#onMouseDown), window.removeEventListener("wheel", this.#onWheel); }; #bindKey = ($elm, key) => { - const buttonIndex = parseInt($elm.getAttribute("data-button-index")), keySlot = parseInt($elm.getAttribute("data-key-slot")); + let buttonIndex = parseInt($elm.getAttribute("data-button-index")), keySlot = parseInt($elm.getAttribute("data-key-slot")); if ($elm.getAttribute("data-key-code") === key.code) return; for (let $otherElm of this.#$.allKeyElements) if ($otherElm.getAttribute("data-key-code") === key.code) this.#unbindKey($otherElm); this.#STATE.editingPresetData.mapping[buttonIndex][keySlot] = key.code, $elm.textContent = key.name, $elm.setAttribute("data-key-code", key.code); }; #unbindKey = ($elm) => { - const buttonIndex = parseInt($elm.getAttribute("data-button-index")), keySlot = parseInt($elm.getAttribute("data-key-slot")); + let buttonIndex = parseInt($elm.getAttribute("data-button-index")), keySlot = parseInt($elm.getAttribute("data-key-slot")); this.#STATE.editingPresetData.mapping[buttonIndex][keySlot] = null, $elm.textContent = "", $elm.removeAttribute("data-key-code"); }; #onWheel = (e) => { @@ -3485,20 +3463,19 @@ class MkbRemapper { }; #switchPreset = (presetId) => { this.#STATE.currentPresetId = presetId; - const presetData = this.#getCurrentPreset().data; + let presetData = this.#getCurrentPreset().data; for (let $elm of this.#$.allKeyElements) { - const buttonIndex = parseInt($elm.getAttribute("data-button-index")), keySlot = parseInt($elm.getAttribute("data-key-slot")), buttonKeys = presetData.mapping[buttonIndex]; + let buttonIndex = parseInt($elm.getAttribute("data-button-index")), keySlot = parseInt($elm.getAttribute("data-key-slot")), buttonKeys = presetData.mapping[buttonIndex]; if (buttonKeys && buttonKeys[keySlot]) $elm.textContent = KeyHelper.codeToKeyName(buttonKeys[keySlot]), $elm.setAttribute("data-key-code", buttonKeys[keySlot]); else $elm.textContent = "", $elm.removeAttribute("data-key-code"); } let key; for (key in this.#$.allMouseElements) { - const $elm = this.#$.allMouseElements[key]; - let value = presetData.mouse[key]; + let $elm = this.#$.allMouseElements[key], value = presetData.mouse[key]; if (typeof value === "undefined") value = MkbPreset.MOUSE_SETTINGS[key].default; "setValue" in $elm && $elm.setValue(value); } - const activated = getPref("mkb_default_preset_id") === this.#STATE.currentPresetId; + let activated = getPref("mkb_default_preset_id") === this.#STATE.currentPresetId; this.#$.activateButton.disabled = activated, this.#$.activateButton.querySelector("span").textContent = activated ? t("activated") : t("activate"); }; #refresh() { @@ -3506,25 +3483,24 @@ class MkbRemapper { this.#$.presetsSelect.removeChild(this.#$.presetsSelect.firstChild); LocalDb.INSTANCE.getPresets().then((presets) => { this.#STATE.presets = presets; - const $fragment = document.createDocumentFragment(); - let defaultPresetId; + let $fragment = document.createDocumentFragment(), defaultPresetId; if (this.#STATE.currentPresetId === 0) this.#STATE.currentPresetId = parseInt(Object.keys(presets)[0]), defaultPresetId = this.#STATE.currentPresetId, setPref("mkb_default_preset_id", defaultPresetId), EmulatedMkbHandler.getInstance().refreshPresetData(); else defaultPresetId = getPref("mkb_default_preset_id"); for (let id2 in presets) { let name = presets[id2].name; if (id2 === defaultPresetId) name = "🎮 " + name; - const $options = CE("option", { value: id2 }, name); + let $options = CE("option", { value: id2 }, name); $options.selected = parseInt(id2) === this.#STATE.currentPresetId, $fragment.appendChild($options); } this.#$.presetsSelect.appendChild($fragment); - const activated = defaultPresetId === this.#STATE.currentPresetId; + let activated = defaultPresetId === this.#STATE.currentPresetId; this.#$.activateButton.disabled = activated, this.#$.activateButton.querySelector("span").textContent = activated ? t("activated") : t("activate"), !this.#STATE.isEditing && this.#switchPreset(this.#STATE.currentPresetId); }); } #toggleEditing = (force) => { if (this.#STATE.isEditing = typeof force !== "undefined" ? force : !this.#STATE.isEditing, this.#$.wrapper.classList.toggle("bx-editing", this.#STATE.isEditing), this.#STATE.isEditing) this.#STATE.editingPresetData = deepClone(this.#getCurrentPreset().data); else this.#STATE.editingPresetData = null; - const childElements = this.#$.wrapper.querySelectorAll("select, button, input"); + let childElements = this.#$.wrapper.querySelectorAll("select, button, input"); for (let $elm of Array.from(childElements)) { if ($elm.parentElement.parentElement.classList.contains("bx-mkb-action-buttons")) continue; let disable = !this.#STATE.isEditing; @@ -3536,7 +3512,7 @@ class MkbRemapper { this.#$.wrapper = CE("div", { class: "bx-mkb-settings" }), this.#$.presetsSelect = CE("select", { tabindex: -1 }), this.#$.presetsSelect.addEventListener("change", (e) => { this.#switchPreset(parseInt(e.target.value)); }); - const promptNewName = (value) => { + let promptNewName = (value) => { let newName = ""; while (!newName) { if (newName = prompt(t("prompt-preset-name"), value), newName === null) return !1; @@ -3548,8 +3524,7 @@ class MkbRemapper { icon: BxIcon.CURSOR_TEXT, tabIndex: -1, onClick: (e) => { - const preset = this.#getCurrentPreset(); - let newName = promptNewName(preset.name); + let preset = this.#getCurrentPreset(), newName = promptNewName(preset.name); if (!newName || newName === preset.name) return; preset.name = newName, LocalDb.INSTANCE.updatePreset(preset).then((id2) => this.#refresh()); } @@ -3569,8 +3544,7 @@ class MkbRemapper { title: t("copy"), tabIndex: -1, onClick: (e) => { - const preset = this.#getCurrentPreset(); - let newName = promptNewName(`${preset.name} (2)`); + let preset = this.#getCurrentPreset(), newName = promptNewName(`${preset.name} (2)`); if (!newName) return; LocalDb.INSTANCE.newPreset(newName, preset.data).then((id2) => { this.#STATE.currentPresetId = id2, this.#refresh(); @@ -3589,11 +3563,9 @@ class MkbRemapper { } })); this.#$.wrapper.appendChild($header); - const $rows = CE("div", { class: "bx-mkb-settings-rows" }, CE("i", { class: "bx-mkb-note" }, t("right-click-to-unbind"))), keysPerButton = 2; + let $rows = CE("div", { class: "bx-mkb-settings-rows" }, CE("i", { class: "bx-mkb-note" }, t("right-click-to-unbind"))), keysPerButton = 2; for (let buttonIndex of this.#BUTTON_ORDERS) { - const [buttonName, buttonPrompt] = GamepadKeyName[buttonIndex]; - let $elm; - const $fragment = document.createDocumentFragment(); + let [buttonName, buttonPrompt] = GamepadKeyName[buttonIndex], $elm, $fragment = document.createDocumentFragment(); for (let i = 0;i < keysPerButton; i++) $elm = CE("button", { type: "button", @@ -3601,15 +3573,13 @@ class MkbRemapper { "data-button-index": buttonIndex, "data-key-slot": i }, " "), $elm.addEventListener("mouseup", this.#onBindingKey), $elm.addEventListener("contextmenu", this.#onContextMenu), $fragment.appendChild($elm), this.#$.allKeyElements.push($elm); - const $keyRow = CE("div", { class: "bx-mkb-key-row" }, CE("label", { title: buttonName }, buttonPrompt), $fragment); + let $keyRow = CE("div", { class: "bx-mkb-key-row" }, CE("label", { title: buttonName }, buttonPrompt), $fragment); $rows.appendChild($keyRow); } $rows.appendChild(CE("i", { class: "bx-mkb-note" }, t("mkb-adjust-ingame-settings"))); - const $mouseSettings = document.createDocumentFragment(); + let $mouseSettings = document.createDocumentFragment(); for (let key in MkbPreset.MOUSE_SETTINGS) { - const setting = MkbPreset.MOUSE_SETTINGS[key], value = setting.default; - let $elm; - const onChange = (e, value2) => { + let setting = MkbPreset.MOUSE_SETTINGS[key], value = setting.default, $elm, onChange = (e, value2) => { this.#STATE.editingPresetData.mouse[key] = value2; }, $row = CE("label", { class: "bx-settings-row", @@ -3618,7 +3588,7 @@ class MkbRemapper { $mouseSettings.appendChild($row), this.#$.allMouseElements[key] = $elm; } $rows.appendChild($mouseSettings), this.#$.wrapper.appendChild($rows); - const $actionButtons = CE("div", { class: "bx-mkb-action-buttons" }, CE("div", {}, createButton({ + let $actionButtons = CE("div", { class: "bx-mkb-action-buttons" }, CE("div", {}, createButton({ label: t("edit"), tabIndex: -1, onClick: (e) => this.#toggleEditing(!0) @@ -3641,7 +3611,7 @@ class MkbRemapper { style: 1, tabIndex: -1, onClick: (e) => { - const updatedPreset = deepClone(this.#getCurrentPreset()); + let updatedPreset = deepClone(this.#getCurrentPreset()); updatedPreset.data = this.#STATE.editingPresetData, LocalDb.INSTANCE.updatePreset(updatedPreset).then((id2) => { if (id2 === getPref("mkb_default_preset_id")) EmulatedMkbHandler.getInstance().refreshPresetData(); this.#toggleEditing(!1), this.#refresh(); @@ -3673,7 +3643,7 @@ class TouchController { TouchController.#xboxTitleId = xboxTitleId; } static getCustomLayouts() { - const xboxTitleId = TouchController.#xboxTitleId; + let xboxTitleId = TouchController.#xboxTitleId; if (!xboxTitleId) return null; return TouchController.#customLayouts[xboxTitleId]; } @@ -3694,7 +3664,7 @@ class TouchController { } static toggleVisibility() { if (!TouchController.#dataChannel) return !1; - const $container = document.querySelector("#BabylonCanvasContainer-main")?.parentElement; + let $container = document.querySelector("#BabylonCanvasContainer-main")?.parentElement; if (!$container) return !1; return $container.classList.toggle("bx-offscreen"), !$container.classList.contains("bx-offscreen"); } @@ -3710,7 +3680,7 @@ class TouchController { TouchController.applyCustomLayout(null, 1000), BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED); } static async requestCustomLayouts(retries = 1) { - const xboxTitleId = TouchController.#xboxTitleId; + let xboxTitleId = TouchController.#xboxTitleId; if (!xboxTitleId) return; if (xboxTitleId in TouchController.#customLayouts) { TouchController.#dispatchLayouts(TouchController.#customLayouts[xboxTitleId]); @@ -3720,14 +3690,14 @@ class TouchController { TouchController.#customLayouts[xboxTitleId] = null, window.setTimeout(() => TouchController.#dispatchLayouts(null), 1000); return; } - const baseUrl = "https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts", url = `${baseUrl}/${xboxTitleId}.json`; + let baseUrl = "https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts", url = `${baseUrl}/${xboxTitleId}.json`; try { - const json = await (await NATIVE_FETCH(url)).json(), layouts = {}; + let json = await (await NATIVE_FETCH(url)).json(), layouts = {}; json.layouts.forEach(async (layoutName) => { let baseLayouts = {}; if (layoutName in TouchController.#baseCustomLayouts) baseLayouts = TouchController.#baseCustomLayouts[layoutName]; else try { - const layoutUrl = `${baseUrl}/layouts/${layoutName}.json`; + let layoutUrl = `${baseUrl}/layouts/${layoutName}.json`; baseLayouts = (await (await NATIVE_FETCH(layoutUrl)).json()).layouts, TouchController.#baseCustomLayouts[layoutName] = baseLayouts; } catch (e) {} Object.assign(layouts, baseLayouts); @@ -3738,13 +3708,13 @@ class TouchController { } static applyCustomLayout(layoutId, delay = 0) { if (!window.BX_EXPOSED.touchLayoutManager) { - const listener = (e) => { + let listener = (e) => { if (window.removeEventListener(BxEvent.TOUCH_LAYOUT_MANAGER_READY, listener), TouchController.#enabled) TouchController.applyCustomLayout(layoutId, 0); }; window.addEventListener(BxEvent.TOUCH_LAYOUT_MANAGER_READY, listener); return; } - const xboxTitleId = TouchController.#xboxTitleId; + let xboxTitleId = TouchController.#xboxTitleId; if (!xboxTitleId) { BxLogger.error(LOG_TAG3, "Invalid xboxTitleId"); return; @@ -3754,18 +3724,18 @@ class TouchController { BxLogger.error(LOG_TAG3, "Invalid layoutId, show default controller"), TouchController.#enabled && TouchController.#showDefault(); return; } - const layoutChanged = TouchController.#currentLayoutId !== layoutId; + let layoutChanged = TouchController.#currentLayoutId !== layoutId; TouchController.#currentLayoutId = layoutId; - const layoutData = TouchController.#customLayouts[xboxTitleId]; + let layoutData = TouchController.#customLayouts[xboxTitleId]; if (!xboxTitleId || !layoutId || !layoutData) { TouchController.#enabled && TouchController.#showDefault(); return; } - const layout = layoutData.layouts[layoutId] || layoutData.layouts[layoutData.default_layout]; + let layout = layoutData.layouts[layoutId] || layoutData.layouts[layoutData.default_layout]; if (!layout) return; let msg, html = !1; if (layout.author) { - const author = `${escapeHtml(layout.author)}`; + let author = `${escapeHtml(layout.author)}`; msg = t("touch-control-layout-by", { name: author }), html = !0; } else msg = t("touch-control-layout"); layoutChanged && Toast.show(msg, layout.name, { html }), window.setTimeout(() => { @@ -3791,7 +3761,7 @@ class TouchController { } static setup() { window.testTouchLayout = (layout) => { - const { touchLayoutManager } = window.BX_EXPOSED; + let { touchLayoutManager } = window.BX_EXPOSED; touchLayoutManager && touchLayoutManager.changeLayoutForScope({ type: "showLayout", scope: "" + TouchController.#xboxTitleId, @@ -3803,11 +3773,11 @@ class TouchController { } }); }; - const $style = document.createElement("style"); + let $style = document.createElement("style"); document.documentElement.appendChild($style), TouchController.#$style = $style; - const PREF_STYLE_STANDARD = getPref("stream_touch_controller_style_standard"), PREF_STYLE_CUSTOM = getPref("stream_touch_controller_style_custom"); + let PREF_STYLE_STANDARD = getPref("stream_touch_controller_style_standard"), PREF_STYLE_CUSTOM = getPref("stream_touch_controller_style_custom"); window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => { - const dataChannel = e.dataChannel; + let dataChannel = e.dataChannel; if (!dataChannel || dataChannel.label !== "message") return; let filter = ""; if (TouchController.#enabled) { @@ -3829,7 +3799,7 @@ class TouchController { } try { if (msg.data.includes("/titleinfo")) { - const json = JSON.parse(JSON.parse(msg.data).content); + let json = JSON.parse(JSON.parse(msg.data).content); if (focused = json.focused, !json.focused) TouchController.#show(); TouchController.setXboxTitleId(parseInt(json.titleid, 16).toString()); } @@ -3854,12 +3824,12 @@ class VibrationManager { AppInterface.vibrate(JSON.stringify(data), window.BX_VIBRATION_INTENSITY); return; } - const intensity = Math.min(100, data.leftMotorPercent + data.rightMotorPercent / 2) * window.BX_VIBRATION_INTENSITY; + let intensity = Math.min(100, data.leftMotorPercent + data.rightMotorPercent / 2) * window.BX_VIBRATION_INTENSITY; if (intensity === 0 || intensity === 100) { window.navigator.vibrate(intensity ? data.durationMs : 0); return; } - const pulseDuration = 200, onDuration = Math.floor(pulseDuration * intensity / 100), offDuration = pulseDuration - onDuration, repeats = Math.ceil(data.durationMs / pulseDuration), pulses = Array(repeats).fill([onDuration, offDuration]).flat(); + let pulseDuration = 200, onDuration = Math.floor(pulseDuration * intensity / 100), offDuration = pulseDuration - onDuration, repeats = Math.ceil(data.durationMs / pulseDuration), pulses = Array(repeats).fill([onDuration, offDuration]).flat(); window.navigator.vibrate(pulses); } static supportControllerVibration() { @@ -3874,12 +3844,11 @@ class VibrationManager { return; } stopVibration && window.navigator.vibrate(0); - const value = getPref("controller_device_vibration"); - let enabled; + let value = getPref("controller_device_vibration"), enabled; if (value === "on") enabled = !0; else if (value === "auto") { enabled = !0; - const gamepads = window.navigator.getGamepads(); + let gamepads = window.navigator.getGamepads(); for (let gamepad of gamepads) if (gamepad) { enabled = !1; @@ -3891,15 +3860,13 @@ class VibrationManager { static #onMessage(e) { if (!window.BX_ENABLE_DEVICE_VIBRATION) return; if (typeof e !== "object" || !(e.data instanceof ArrayBuffer)) return; - const dataView = new DataView(e.data); - let offset = 0, messageType; + let dataView = new DataView(e.data), offset = 0, messageType; if (dataView.byteLength === 13) messageType = dataView.getUint16(offset, !0), offset += Uint16Array.BYTES_PER_ELEMENT; else messageType = dataView.getUint8(offset), offset += Uint8Array.BYTES_PER_ELEMENT; if (!(messageType & 128)) return; - const vibrationType = dataView.getUint8(offset); + let vibrationType = dataView.getUint8(offset); if (offset += Uint8Array.BYTES_PER_ELEMENT, vibrationType !== 0) return; - const data = {}; - let key; + let data = {}, key; for (key in VIBRATION_DATA_MAP) if (VIBRATION_DATA_MAP[key] === 16) data[key] = dataView.getUint16(offset, !0), offset += Uint16Array.BYTES_PER_ELEMENT; else data[key] = dataView.getUint8(offset), offset += Uint8Array.BYTES_PER_ELEMENT; @@ -3907,7 +3874,7 @@ class VibrationManager { } static initialSetup() { window.addEventListener("gamepadconnected", (e) => VibrationManager.updateGlobalVars()), window.addEventListener("gamepaddisconnected", (e) => VibrationManager.updateGlobalVars()), VibrationManager.updateGlobalVars(!1), window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => { - const dataChannel = e.dataChannel; + let dataChannel = e.dataChannel; if (!dataChannel || dataChannel.label !== "input") return; dataChannel.addEventListener("message", VibrationManager.#onMessage); }); @@ -3931,12 +3898,12 @@ if (getPref("block_social_features")) FeatureGates.EnableGuideChatTab = !1; if (BX_FLAGS.FeatureGates) FeatureGates = Object.assign(BX_FLAGS.FeatureGates, FeatureGates); class PatcherUtils { static indexOf(txt, searchString, startIndex, maxRange) { - const index = txt.indexOf(searchString, startIndex); + let index = txt.indexOf(searchString, startIndex); if (index < 0 || maxRange && index - startIndex > maxRange) return -1; return index; } static lastIndexOf(txt, searchString, startIndex, maxRange) { - const index = txt.lastIndexOf(searchString, startIndex); + let index = txt.lastIndexOf(searchString, startIndex); if (index < 0 || maxRange && startIndex - index > maxRange) return -1; return index; } @@ -3949,8 +3916,7 @@ class PatcherUtils { } var ENDING_CHUNKS_PATCH_NAME = "loadingEndingChunks", LOG_TAG4 = "Patcher", PATCHES = { disableAiTrack(str) { - let text = ".track=function("; - const index = str.indexOf(text); + let text = ".track=function(", index = str.indexOf(text); if (index < 0) return !1; if (PatcherUtils.indexOf(str, '"AppInsightsCore', index, 200) < 0) return !1; return PatcherUtils.replaceWith(str, index, text, ".track=function(e){},!!function("); @@ -3963,7 +3929,7 @@ var ENDING_CHUNKS_PATCH_NAME = "loadingEndingChunks", LOG_TAG4 = "Patcher", PATC disableTelemetryProvider(str) { let text = "this.enableLightweightTelemetry=!"; if (!str.includes(text)) return !1; - const newCode = [ + let newCode = [ "this.trackEvent", "this.trackPageView", "this.trackHttpCompleted", @@ -3984,11 +3950,11 @@ var ENDING_CHUNKS_PATCH_NAME = "loadingEndingChunks", LOG_TAG4 = "Patcher", PATC websiteLayout(str) { let text = '?"tv":"default"'; if (!str.includes(text)) return !1; - const layout = getPref("ui_layout") === "tv" ? "tv" : "default"; + let layout = getPref("ui_layout") === "tv" ? "tv" : "default"; return str.replace(text, `?"${layout}":"${layout}"`); }, remotePlayDirectConnectUrl(str) { - const index = str.indexOf("/direct-connect"); + let index = str.indexOf("/direct-connect"); if (index < 0) return !1; return str.replace(str.substring(index - 9, index + 15), "https://www.xbox.com/play"); }, @@ -4005,13 +3971,13 @@ var ENDING_CHUNKS_PATCH_NAME = "loadingEndingChunks", LOG_TAG4 = "Patcher", PATC remotePlayDisableAchievementToast(str) { let text = ".AchievementUnlock:{"; if (!str.includes(text)) return !1; - const newCode = "if (!!window.BX_REMOTE_PLAY_CONFIG) return;"; + let newCode = "if (!!window.BX_REMOTE_PLAY_CONFIG) return;"; return str.replace(text, text + newCode); }, remotePlayRecentlyUsedTitleIds(str) { let text = "(e.data.recentlyUsedTitleIds)){"; if (!str.includes(text)) return !1; - const newCode = "if (window.BX_REMOTE_PLAY_CONFIG) return;"; + let newCode = "if (window.BX_REMOTE_PLAY_CONFIG) return;"; return str.replace(text, text + newCode); }, blockWebRtcStatsCollector(str) { @@ -4020,15 +3986,15 @@ var ENDING_CHUNKS_PATCH_NAME = "loadingEndingChunks", LOG_TAG4 = "Patcher", PATC return str.replace(text, "this.shouldCollectStats=!1"); }, patchPollGamepads(str) { - const index = str.indexOf("},this.pollGamepads=()=>{"); + let index = str.indexOf("},this.pollGamepads=()=>{"); if (index < 0) return !1; - const nextIndex = str.indexOf("setTimeout(this.pollGamepads", index); + let nextIndex = str.indexOf("setTimeout(this.pollGamepads", index); if (nextIndex === -1) return !1; let codeBlock = str.substring(index, nextIndex); if (getPref("block_tracking")) codeBlock = codeBlock.replace("this.inputPollingIntervalStats.addValue", ""), codeBlock = codeBlock.replace("this.inputPollingDurationStats.addValue", ""); - const match = codeBlock.match(/this\.gamepadTimestamps\.set\((\w+)\.index/); + let match = codeBlock.match(/this\.gamepadTimestamps\.set\((\w+)\.index/); if (match) { - const gamepadVar = match[1], newCode = renderString(controller_shortcuts_default, { + let gamepadVar = match[1], newCode = renderString(controller_shortcuts_default, { gamepadVar }); codeBlock = codeBlock.replace("this.gamepadTimestamps.set", newCode + "this.gamepadTimestamps.set"); @@ -4038,7 +4004,7 @@ var ENDING_CHUNKS_PATCH_NAME = "loadingEndingChunks", LOG_TAG4 = "Patcher", PATC enableXcloudLogger(str) { let text = "this.telemetryProvider=e}log(e,t,r){"; if (!str.includes(text)) return !1; - const newCode = ` + let newCode = ` const [logTag, logLevel, logMessage] = Array.from(arguments); const logFunc = [console.debug, console.log, console.warn, console.error][logLevel]; logFunc(logTag, '//', logMessage); @@ -4056,24 +4022,23 @@ logFunc(logTag, '//', logMessage); return VibrationManager.updateGlobalVars(), str = str.replaceAll(text, text + vibration_adjust_default), str; }, overrideSettings(str) { - const index = str.indexOf(",EnableStreamGate:"); + let index = str.indexOf(",EnableStreamGate:"); if (index < 0) return !1; - const endIndex = str.indexOf("},", index); - let newSettings = JSON.stringify(FeatureGates); + let endIndex = str.indexOf("},", index), newSettings = JSON.stringify(FeatureGates); newSettings = newSettings.substring(1, newSettings.length - 1); - const newCode = newSettings; + let newCode = newSettings; return str = str.substring(0, endIndex) + "," + newCode + str.substring(endIndex), str; }, disableGamepadDisconnectedScreen(str) { - const index = str.indexOf('"GamepadDisconnected_Title",'); + let index = str.indexOf('"GamepadDisconnected_Title",'); if (index < 0) return !1; - const constIndex = str.indexOf("const", index - 30); + let constIndex = str.indexOf("const", index - 30); return str = str.substring(0, constIndex) + "e.onClose();return null;" + str.substring(constIndex), str; }, patchUpdateInputConfigurationAsync(str) { let text = "async updateInputConfigurationAsync(e){"; if (!str.includes(text)) return !1; - const newCode = "e.enableTouchInput = true;"; + let newCode = "e.enableTouchInput = true;"; return str = str.replace(text, text + newCode), str; }, loadingEndingChunks(str) { @@ -4082,15 +4047,15 @@ logFunc(logTag, '//', logMessage); return BxLogger.info(LOG_TAG4, "Remaining patches:", PATCH_ORDERS), PATCH_ORDERS = PATCH_ORDERS.concat(PLAYING_PATCH_ORDERS), str; }, disableStreamGate(str) { - const index = str.indexOf('case"partially-ready":'); + let index = str.indexOf('case"partially-ready":'); if (index < 0) return !1; - const bracketIndex = str.indexOf("=>{", index - 150) + 3; + let bracketIndex = str.indexOf("=>{", index - 150) + 3; return str = str.substring(0, bracketIndex) + "return 0;" + str.substring(bracketIndex), str; }, exposeTouchLayoutManager(str) { let text = "this._perScopeLayoutsStream=new"; if (!str.includes(text)) return !1; - const newCode = ` + let newCode = ` true; window.BX_EXPOSED["touchLayoutManager"] = this; window.dispatchEvent(new Event("${BxEvent.TOUCH_LAYOUT_MANAGER_READY}")); @@ -4101,7 +4066,7 @@ window.dispatchEvent(new Event("${BxEvent.TOUCH_LAYOUT_MANAGER_READY}")); let index = str.indexOf(".current.render(),"); if (index < 0) return !1; index -= 1; - const newCode = ` + let newCode = ` if (window.BX_EXPOSED.stopTakRendering) { try { document.getElementById('BabylonCanvasContainer-main')?.parentElement.classList.add('bx-offscreen'); @@ -4116,13 +4081,13 @@ if (window.BX_EXPOSED.stopTakRendering) { supportLocalCoOp(str) { let text = "this.gamepadMappingsToSend=[],"; if (!str.includes(text)) return !1; - const newCode = `true; ${local_co_op_enable_default}; true,`; + let newCode = `true; ${local_co_op_enable_default}; true,`; return str = str.replace(text, text + newCode), str; }, forceFortniteConsole(str) { let text = "sendTouchInputEnabledMessage(e){"; if (!str.includes(text)) return !1; - const newCode = "window.location.pathname.includes('/launch/fortnite/') && (e = false);"; + let newCode = "window.location.pathname.includes('/launch/fortnite/') && (e = false);"; return str = str.replace(text, text + newCode), str; }, disableTakRenderer(str) { @@ -4143,7 +4108,7 @@ if (gamepadFound) { return; } `; - const newCode = ` + let newCode = ` ${autoOffCode} const titleInfo = window.BX_EXPOSED.getTitleInfo(); if (titleInfo && !titleInfo.details.hasTouchSupport && !titleInfo.details.hasFakeTouchSupport) { @@ -4172,7 +4137,7 @@ e.guideUI = null; broadcastPollingMode(str) { let text = ".setPollingMode=e=>{"; if (!str.includes(text)) return !1; - const newCode = ` + let newCode = ` BxEvent.dispatch(window, BxEvent.XCLOUD_POLLING_MODE_CHANGED, {mode: e.toLowerCase()}); `; return str = str.replace(text, text + newCode), str; @@ -4185,8 +4150,7 @@ BxEvent.dispatch(window, BxEvent.XCLOUD_POLLING_MODE_CHANGED, {mode: e.toLowerCa patchXcloudTitleInfo(str) { let text = "async cloudConnect", index = str.indexOf(text); if (index < 0) return !1; - let backetIndex = str.indexOf("{", index); - const titleInfoVar = str.substring(index, backetIndex).match(/\(([^)]+)\)/)[1].split(",")[0], newCode = ` + let backetIndex = str.indexOf("{", index), titleInfoVar = str.substring(index, backetIndex).match(/\(([^)]+)\)/)[1].split(",")[0], newCode = ` ${titleInfoVar} = window.BX_EXPOSED.modifyTitleInfo(${titleInfoVar}); BxLogger.info('patchXcloudTitleInfo', ${titleInfoVar}); `; @@ -4195,8 +4159,7 @@ BxLogger.info('patchXcloudTitleInfo', ${titleInfoVar}); patchRemotePlayMkb(str) { let text = "async homeConsoleConnect", index = str.indexOf(text); if (index < 0) return !1; - let backetIndex = str.indexOf("{", index); - const configsVar = str.substring(index, backetIndex).match(/\(([^)]+)\)/)[1].split(",")[1], newCode = ` + let backetIndex = str.indexOf("{", index), configsVar = str.substring(index, backetIndex).match(/\(([^)]+)\)/)[1].split(",")[1], newCode = ` Object.assign(${configsVar}.inputConfiguration, { enableMouseInput: false, enableKeyboardInput: false, @@ -4209,31 +4172,31 @@ BxLogger.info('patchRemotePlayMkb', ${configsVar}); patchAudioMediaStream(str) { let text = ".srcObject=this.audioMediaStream,"; if (!str.includes(text)) return !1; - const newCode = "window.BX_EXPOSED.setupGainNode(arguments[1], this.audioMediaStream),"; + let newCode = "window.BX_EXPOSED.setupGainNode(arguments[1], this.audioMediaStream),"; return str = str.replace(text, text + newCode), str; }, patchCombinedAudioVideoMediaStream(str) { let text = ".srcObject=this.combinedAudioVideoStream"; if (!str.includes(text)) return !1; - const newCode = ",window.BX_EXPOSED.setupGainNode(arguments[0], this.combinedAudioVideoStream)"; + let newCode = ",window.BX_EXPOSED.setupGainNode(arguments[0], this.combinedAudioVideoStream)"; return str = str.replace(text, text + newCode), str; }, patchTouchControlDefaultOpacity(str) { let text = "opacityMultiplier:1"; if (!str.includes(text)) return !1; - const newCode = `opacityMultiplier: ${(getPref("stream_touch_controller_default_opacity") / 100).toFixed(1)}`; + let newCode = `opacityMultiplier: ${(getPref("stream_touch_controller_default_opacity") / 100).toFixed(1)}`; return str = str.replace(text, newCode), str; }, patchShowSensorControls(str) { let text = "{shouldShowSensorControls:"; if (!str.includes(text)) return !1; - const newCode = "{shouldShowSensorControls: (window.BX_EXPOSED && window.BX_EXPOSED.shouldShowSensorControls) ||"; + let newCode = "{shouldShowSensorControls: (window.BX_EXPOSED && window.BX_EXPOSED.shouldShowSensorControls) ||"; return str = str.replace(text, newCode), str; }, exposeStreamSession(str) { let text = ",this._connectionType="; if (!str.includes(text)) return !1; - const newCode = `; + let newCode = `; ${expose_stream_session_default} true` + text; return str = str.replace(text, newCode), str; @@ -4256,7 +4219,7 @@ true` + text; exposeInputSink(str) { let text = "this.controlChannel=null,this.inputChannel=null"; if (!str.includes(text)) return !1; - const newCode = "window.BX_EXPOSED.inputSink = this;"; + let newCode = "window.BX_EXPOSED.inputSink = this;"; return str = str.replace(text, newCode + text), str; }, disableNativeRequestPointerLock(str) { @@ -4277,9 +4240,9 @@ true` + text; enableTvRoutes(str) { let index = str.indexOf(".LoginDeviceCode.path,"); if (index < 0) return !1; - const match = /render:.*?jsx\)\(([^,]+),/.exec(str.substring(index, index + 100)); + let match = /render:.*?jsx\)\(([^,]+),/.exec(str.substring(index, index + 100)); if (!match) return !1; - const funcName = match[1]; + let funcName = match[1]; if (index = str.indexOf(`const ${funcName}=e=>{`), index > -1 && (index = str.indexOf("return ", index)), index > -1 && (index = str.indexOf("?", index)), index < 0) return !1; return str = str.substring(0, index) + "|| true" + str.substring(index), str; }, @@ -4306,15 +4269,15 @@ true` + text; let index = str.indexOf("SiglRow-module__heroCard___"); if (index < 0) return !1; if (index = PatcherUtils.lastIndexOf(str, "const[", index, 300), index < 0) return !1; - const PREF_HIDE_SECTIONS = getPref("ui_hide_sections"), siglIds = [], sections = { + let PREF_HIDE_SECTIONS = getPref("ui_hide_sections"), siglIds = [], sections = { "native-mkb": "8fa264dd-124f-4af3-97e8-596fcdf4b486", "most-popular": "e7590b22-e299-44db-ae22-25c61405454c" }; PREF_HIDE_SECTIONS.forEach((section) => { - const galleryId = sections[section]; + let galleryId = sections[section]; galleryId && siglIds.push(galleryId); }); - const newCode = ` + let newCode = ` if (e && e.id) { const siglId = e.id; if (${siglIds.map((item2) => `siglId === "${item2}"`).join(" || ")}) { @@ -4327,7 +4290,7 @@ if (e && e.id) { overrideStorageGetSettings(str) { let text = "}getSetting(e){"; if (!str.includes(text)) return !1; - const newCode = ` + let newCode = ` // console.log('setting', this.baseStorageKey, e); if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) { const settings = window.BX_EXPOSED.overrideSettings[this.baseStorageKey]; @@ -4342,7 +4305,7 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) { let index = str.indexOf(",{onShowStreamMenu:"); if (index < 0) return !1; if (index = str.indexOf("&&(0,", index - 100), index < 0) return !1; - const commaIndex = str.indexOf(",", index - 10); + let commaIndex = str.indexOf(",", index - 10); return str = str.substring(0, commaIndex) + ",true" + str.substring(index), str; }, patchSetCurrentlyFocusedInteractable(str) { @@ -4441,7 +4404,7 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) { ].filter((item2) => !!item2), ALL_PATCHES = [...PATCH_ORDERS, ...PLAYING_PATCH_ORDERS]; class Patcher { static #patchFunctionBind() { - const nativeBind = Function.prototype.bind; + let nativeBind = Function.prototype.bind; Function.prototype.bind = function() { let valid = !1; if (this.name.length <= 2 && arguments.length === 2 && arguments[0] === null) { @@ -4449,28 +4412,26 @@ class Patcher { } if (!valid) return nativeBind.apply(this, arguments); if (PatcherCache.init(), typeof arguments[1] === "function") BxLogger.info(LOG_TAG4, "Restored Function.prototype.bind()"), Function.prototype.bind = nativeBind; - const orgFunc = this, newFunc = (a, item2) => { + let orgFunc = this, newFunc = (a, item2) => { Patcher.patch(item2), orgFunc(a, item2); }; return nativeBind.apply(newFunc, arguments); }; } static patch(item) { - let patchesToCheck, appliedPatches; - const patchesMap = {}; + let patchesToCheck, appliedPatches, patchesMap = {}; for (let id in item[1]) { appliedPatches = []; - const cachedPatches = PatcherCache.getPatches(id); + let cachedPatches = PatcherCache.getPatches(id); if (cachedPatches) patchesToCheck = cachedPatches.slice(0), patchesToCheck.push(...PATCH_ORDERS); else patchesToCheck = PATCH_ORDERS.slice(0); if (!patchesToCheck.length) continue; - const func = item[1][id], funcStr = func.toString(); - let patchedFuncStr = funcStr, modified = !1; + let func = item[1][id], funcStr = func.toString(), patchedFuncStr = funcStr, modified = !1; for (let patchIndex = 0;patchIndex < patchesToCheck.length; patchIndex++) { - const patchName = patchesToCheck[patchIndex]; + let patchName = patchesToCheck[patchIndex]; if (appliedPatches.indexOf(patchName) > -1) continue; if (!PATCHES[patchName]) continue; - const tmpStr = PATCHES[patchName].call(null, patchedFuncStr); + let tmpStr = PATCHES[patchName].call(null, patchedFuncStr); if (!tmpStr) continue; modified = !0, patchedFuncStr = tmpStr, BxLogger.info(LOG_TAG4, `✅ ${patchName}`), appliedPatches.push(patchName), patchesToCheck.splice(patchIndex, 1), patchIndex--, PATCH_ORDERS = PATCH_ORDERS.filter((item2) => item2 != patchName); } @@ -4493,14 +4454,14 @@ class PatcherCache { static #CACHE; static #isInitialized = !1; static #getSignature() { - const scriptVersion = SCRIPT_VERSION, webVersion = document.querySelector("meta[name=gamepass-app-version]")?.content, patches = JSON.stringify(ALL_PATCHES); + let scriptVersion = SCRIPT_VERSION, webVersion = document.querySelector("meta[name=gamepass-app-version]")?.content, patches = JSON.stringify(ALL_PATCHES); return hashCode(scriptVersion + webVersion + patches); } static clear() { window.localStorage.removeItem(PatcherCache.#KEY_CACHE), PatcherCache.#CACHE = {}; } static checkSignature() { - const storedSig = window.localStorage.getItem(PatcherCache.#KEY_SIGNATURE) || 0, currentSig = PatcherCache.#getSignature(); + let storedSig = window.localStorage.getItem(PatcherCache.#KEY_SIGNATURE) || 0, currentSig = PatcherCache.#getSignature(); if (currentSig !== parseInt(storedSig)) BxLogger.warning(LOG_TAG4, "Signature changed"), window.localStorage.setItem(PatcherCache.#KEY_SIGNATURE, currentSig.toString()), PatcherCache.clear(); else BxLogger.info(LOG_TAG4, "Signature unchanged"); } @@ -4516,8 +4477,7 @@ class PatcherCache { } static saveToCache(subCache) { for (let id2 in subCache) { - const patchNames = subCache[id2]; - let data = PatcherCache.#CACHE[id2]; + let patchNames = subCache[id2], data = PatcherCache.#CACHE[id2]; if (!data) PatcherCache.#CACHE[id2] = patchNames; else for (let patchName of patchNames) if (!data.includes(patchName)) data.push(patchName); @@ -4572,9 +4532,9 @@ class SettingsNavigationDialog extends NavigationDialog { helpUrl: "https://better-xcloud.github.io/features/", items: [ ($parent) => { - const PREF_LATEST_VERSION = getPref("version_latest"), topButtons = []; + let PREF_LATEST_VERSION = getPref("version_latest"), topButtons = []; if (!SCRIPT_VERSION.includes("beta") && PREF_LATEST_VERSION && PREF_LATEST_VERSION != SCRIPT_VERSION) { - const opts = { + let opts = { label: "🌟 " + t("new-version-available", { version: PREF_LATEST_VERSION }), style: 1 | 32 | 64 }; @@ -4608,7 +4568,7 @@ class SettingsNavigationDialog extends NavigationDialog { class: "bx-suggest-toggler bx-focusable", tabindex: 0 }, CE("label", {}, t("suggest-settings")), CE("span", {}, "❯")), this.$btnSuggestion.addEventListener("click", this.renderSuggestions.bind(this)), topButtons.push(this.$btnSuggestion); - const $div = CE("div", { + let $div = CE("div", { class: "bx-top-buttons", _nearby: { orientation: "vertical" @@ -4722,7 +4682,7 @@ class SettingsNavigationDialog extends NavigationDialog { { pref: "user_agent_profile", onCreated: (setting, $control) => { - const defaultUserAgent = window.navigator.orgUserAgent || window.navigator.userAgent, $inpCustomUserAgent = CE("input", { + let defaultUserAgent = window.navigator.orgUserAgent || window.navigator.userAgent, $inpCustomUserAgent = CE("input", { id: `bx_setting_inp_${setting.pref}`, type: "text", placeholder: defaultUserAgent, @@ -4731,7 +4691,7 @@ class SettingsNavigationDialog extends NavigationDialog { tabindex: 0 }); $inpCustomUserAgent.addEventListener("input", (e) => { - const profile = $control.value, custom = e.target.value.trim(); + let profile = $control.value, custom = e.target.value.trim(); UserAgent.updateStorage(profile, custom), this.onGlobalSettingChanged(e); }), $control.insertAdjacentElement("afterend", $inpCustomUserAgent), setNearby($inpCustomUserAgent.parentElement, { orientation: "vertical" @@ -4752,20 +4712,20 @@ class SettingsNavigationDialog extends NavigationDialog { }, ($parent) => { try { - const appVersion = document.querySelector("meta[name=gamepass-app-version]").content, appDate = new Date(document.querySelector("meta[name=gamepass-app-date]").content).toISOString().substring(0, 10); + let appVersion = document.querySelector("meta[name=gamepass-app-version]").content, appDate = new Date(document.querySelector("meta[name=gamepass-app-date]").content).toISOString().substring(0, 10); $parent.appendChild(CE("div", { class: "bx-settings-app-version" }, `xCloud website version ${appVersion} (${appDate})`)); } catch (e) {} }, ($parent) => { - const debugInfo = deepClone(BX_FLAGS.DeviceInfo); + let debugInfo = deepClone(BX_FLAGS.DeviceInfo); debugInfo.settings = JSON.parse(window.localStorage.getItem("better_xcloud") || "{}"); - const $debugInfo = CE("div", { class: "bx-debug-info" }, createButton({ + let $debugInfo = CE("div", { class: "bx-debug-info" }, createButton({ label: "Debug info", style: 4 | 64 | 32, onClick: (e) => { - const $pre = e.target.closest("button")?.nextElementSibling; + let $pre = e.target.closest("button")?.nextElementSibling; $pre.classList.toggle("bx-gone"), $pre.scrollIntoView(); } }), CE("pre", { @@ -4795,9 +4755,9 @@ class SettingsNavigationDialog extends NavigationDialog { disabled: !getPref("audio_enable_volume_control") }, onCreated: (setting, $elm) => { - const $range = $elm.querySelector("input[type=range"); + let $range = $elm.querySelector("input[type=range"); window.addEventListener(BxEvent.SETTINGS_CHANGED, (e) => { - const { storageKey, settingKey, settingValue } = e; + let { storageKey, settingKey, settingValue } = e; if (storageKey !== "better_xcloud" || settingKey !== "audio_volume") return; $range.value = settingValue, BxEvent.dispatch($range, "input", { ignoreOnChange: !0 @@ -4820,7 +4780,7 @@ class SettingsNavigationDialog extends NavigationDialog { }, { pref: "video_power_preference", onChange: () => { - const streamPlayer = STATES.currentStream.streamPlayer; + let streamPlayer = STATES.currentStream.streamPlayer; if (!streamPlayer) return; streamPlayer.reloadPlayer(), updateVideoPlayer(); } @@ -4875,20 +4835,19 @@ class SettingsNavigationDialog extends NavigationDialog { $elm.addEventListener("input", (e) => { TouchController.applyCustomLayout($elm.value, 1000); }), window.addEventListener(BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, (e) => { - const customLayouts = TouchController.getCustomLayouts(); + let customLayouts = TouchController.getCustomLayouts(); while ($elm.firstChild) $elm.removeChild($elm.firstChild); if ($elm.disabled = !customLayouts, !customLayouts) { $elm.appendChild(CE("option", { value: "" }, t("default"))), $elm.value = "", $elm.dispatchEvent(new Event("input")); return; } - const $fragment = document.createDocumentFragment(); + let $fragment = document.createDocumentFragment(); for (let key in customLayouts.layouts) { - const layout = customLayouts.layouts[key]; - let name; + let layout = customLayouts.layouts[key], name; if (layout.author) name = `${layout.name} (${layout.author})`; else name = layout.name; - const $option = CE("option", { value: key }, name); + let $option = CE("option", { value: key }, name); $fragment.appendChild($option); } $elm.appendChild($fragment), $elm.value = customLayouts.default_layout; @@ -4936,7 +4895,7 @@ class SettingsNavigationDialog extends NavigationDialog { { pref: "stats_quick_glance", onChange: (e) => { - const streamStats = StreamStats.getInstance(); + let streamStats = StreamStats.getInstance(); e.target.checked ? streamStats.quickGlanceSetup() : streamStats.quickGlanceStop(); } }, @@ -5020,7 +4979,7 @@ class SettingsNavigationDialog extends NavigationDialog { onMounted() { if (!this.renderFullSettings) return; if (onChangeVideoPlayerType(), STATES.userAgent.capabilities.touch) BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED); - const $selectUserAgent = document.querySelector(`#bx_setting_${"user_agent_profile"}`); + let $selectUserAgent = document.querySelector(`#bx_setting_${"user_agent_profile"}`); if ($selectUserAgent) $selectUserAgent.disabled = !0, BxEvent.dispatch($selectUserAgent, "input", {}), $selectUserAgent.disabled = !1; } reloadPage() { @@ -5028,9 +4987,9 @@ class SettingsNavigationDialog extends NavigationDialog { } async getRecommendedSettings(deviceCode) { try { - const json = await (await NATIVE_FETCH(`https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/devices/${deviceCode.toLowerCase()}.json`)).json(), recommended = {}; + let json = await (await NATIVE_FETCH(`https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/devices/${deviceCode.toLowerCase()}.json`)).json(), recommended = {}; if (json.schema_version !== 1) return null; - const scriptSettings = json.settings.script; + let scriptSettings = json.settings.script; if (scriptSettings._base) { let base = typeof scriptSettings._base === "string" ? [scriptSettings._base] : scriptSettings._base; for (let profile of base) @@ -5063,7 +5022,7 @@ class SettingsNavigationDialog extends NavigationDialog { return requiredVariants = typeof requiredVariants === "string" ? [requiredVariants] : requiredVariants, requiredVariants.includes(SCRIPT_VARIANT); } async renderSuggestions(e) { - const $btnSuggest = e.target.closest("div"); + let $btnSuggest = e.target.closest("div"); $btnSuggest.toggleAttribute("bx-open"); let $content = $btnSuggest.nextElementSibling; if ($content) { @@ -5085,36 +5044,32 @@ class SettingsNavigationDialog extends NavigationDialog { let recommendedDevice = ""; if (BX_FLAGS.DeviceInfo.deviceType.includes("android")) { if (BX_FLAGS.DeviceInfo.androidInfo) { - const deviceCode = BX_FLAGS.DeviceInfo.androidInfo.board; + let deviceCode = BX_FLAGS.DeviceInfo.androidInfo.board; recommendedDevice = await this.getRecommendedSettings(deviceCode); } } - const hasRecommendedSettings = Object.keys(this.suggestedSettings.recommended).length > 0, deviceType = BX_FLAGS.DeviceInfo.deviceType; + let hasRecommendedSettings = Object.keys(this.suggestedSettings.recommended).length > 0, deviceType = BX_FLAGS.DeviceInfo.deviceType; if (deviceType === "android-handheld") this.addDefaultSuggestedSetting("stream_touch_controller", "off"), this.addDefaultSuggestedSetting("controller_device_vibration", "on"); else if (deviceType === "android") this.addDefaultSuggestedSetting("controller_device_vibration", "auto"); else if (deviceType === "android-tv") this.addDefaultSuggestedSetting("stream_touch_controller", "off"); this.generateDefaultSuggestedSettings(); - const $suggestedSettings = CE("div", { class: "bx-suggest-wrapper" }), $select = CE("select", {}, hasRecommendedSettings && CE("option", { value: "recommended" }, t("recommended")), !hasRecommendedSettings && CE("option", { value: "highest" }, t("highest-quality")), CE("option", { value: "default" }, t("default")), CE("option", { value: "lowest" }, t("lowest-quality"))); + let $suggestedSettings = CE("div", { class: "bx-suggest-wrapper" }), $select = CE("select", {}, hasRecommendedSettings && CE("option", { value: "recommended" }, t("recommended")), !hasRecommendedSettings && CE("option", { value: "highest" }, t("highest-quality")), CE("option", { value: "default" }, t("default")), CE("option", { value: "lowest" }, t("lowest-quality"))); $select.addEventListener("input", (e2) => { - const profile = $select.value; + let profile = $select.value; removeChildElements($suggestedSettings); - const fragment = document.createDocumentFragment(); - let note; + let fragment = document.createDocumentFragment(), note; if (profile === "recommended") note = t("recommended-settings-for-device", { device: recommendedDevice }); else if (profile === "highest") note = "⚠️ " + t("highest-quality-note"); note && fragment.appendChild(CE("div", { class: "bx-suggest-note" }, note)); - const settings = this.suggestedSettings[profile]; - let prefKey; + let settings = this.suggestedSettings[profile], prefKey; for (prefKey in settings) { - const currentValue = getPref(prefKey, !1), suggestedValue = settings[prefKey], currentValueText = STORAGE.Global.getValueText(prefKey, currentValue), isSameValue = currentValue === suggestedValue; - let $child, $value; + let currentValue = getPref(prefKey, !1), suggestedValue = settings[prefKey], currentValueText = STORAGE.Global.getValueText(prefKey, currentValue), isSameValue = currentValue === suggestedValue, $child, $value; if (isSameValue) $value = currentValueText; else { - const suggestedValueText = STORAGE.Global.getValueText(prefKey, suggestedValue); + let suggestedValueText = STORAGE.Global.getValueText(prefKey, suggestedValue); $value = currentValueText + " ➔ " + suggestedValueText; } - let $checkbox; - const breadcrumb = this.suggestedSettingLabels[prefKey] + " ❯ " + STORAGE.Global.getLabel(prefKey); + let $checkbox, breadcrumb = this.suggestedSettingLabels[prefKey] + " ❯ " + STORAGE.Global.getLabel(prefKey); if ($child = CE("div", { class: `bx-suggest-row ${isSameValue ? "bx-suggest-ok" : "bx-suggest-change"}` }, $checkbox = CE("input", { @@ -5134,13 +5089,12 @@ class SettingsNavigationDialog extends NavigationDialog { } $suggestedSettings.appendChild(fragment); }), BxEvent.dispatch($select, "input"); - const onClickApply = () => { - const profile = $select.value, settings = this.suggestedSettings[profile]; - let prefKey; + let onClickApply = () => { + let profile = $select.value, settings = this.suggestedSettings[profile], prefKey; for (prefKey in settings) { - const suggestedValue = settings[prefKey], $checkBox = $content.querySelector(`#bx_suggest_${prefKey}`); + let suggestedValue = settings[prefKey], $checkBox = $content.querySelector(`#bx_suggest_${prefKey}`); if (!$checkBox.checked || $checkBox.disabled) continue; - const $control = this.settingElements[prefKey]; + let $control = this.settingElements[prefKey]; if (!$control) { setPref(prefKey, suggestedValue); continue; @@ -5175,7 +5129,7 @@ class SettingsNavigationDialog extends NavigationDialog { }, t("suggest-settings-link"))), $btnSuggest?.insertAdjacentElement("afterend", $content); } renderTab(settingTab) { - const $svg = createSvgIcon(settingTab.icon); + let $svg = createSvgIcon(settingTab.icon); return $svg.dataset.group = settingTab.group, $svg.tabIndex = 0, $svg.addEventListener("click", (e) => { for (let $child of Array.from(this.$settings.children)) if ($child.getAttribute("data-tab-group") === settingTab.group) { @@ -5190,8 +5144,7 @@ class SettingsNavigationDialog extends NavigationDialog { PatcherCache.clear(), this.$btnReload.classList.add("bx-danger"), this.$noteGlobalReload.classList.add("bx-gone"), this.$btnGlobalReload.classList.remove("bx-gone"), this.$btnGlobalReload.classList.add("bx-danger"); } renderServerSetting(setting) { - let selectedValue; - const $control = CE("select", { + let selectedValue, $control = CE("select", { id: `bx_setting_${setting.pref}`, title: setting.label, tabindex: 0 @@ -5200,15 +5153,14 @@ class SettingsNavigationDialog extends NavigationDialog { setPref(setting.pref, e.target.value), this.onGlobalSettingChanged(e); }), selectedValue = getPref("server_region"), setting.options = {}; for (let regionName in STATES.serverRegions) { - const region = STATES.serverRegions[regionName]; - let value = regionName, label = `${region.shortName} - ${regionName}`; + let region = STATES.serverRegions[regionName], value = regionName, label = `${region.shortName} - ${regionName}`; if (region.isDefault) { if (label += ` (${t("default")})`, value = "default", selectedValue === regionName) selectedValue = "default"; } setting.options[value] = label; } for (let value in setting.options) { - const label = setting.options[value], $option = CE("option", { value }, label); + let label = setting.options[value], $option = CE("option", { value }, label); $control.appendChild($option); } return $control.disabled = Object.keys(STATES.serverRegions).length === 0, $control.value = selectedValue, $control; @@ -5217,14 +5169,13 @@ class SettingsNavigationDialog extends NavigationDialog { if (typeof setting === "string") setting = { pref: setting }; - const pref = setting.pref; - let $control; + let pref = setting.pref, $control; if (setting.content) if (typeof setting.content === "function") $control = setting.content.apply(this); else $control = setting.content; else if (!setting.unsupported) { if (pref === "server_region") $control = this.renderServerSetting(setting); else if (pref === "bx_locale") $control = SettingElement.fromPref(pref, STORAGE.Global, async (e) => { - const newLocale = e.target.value; + let newLocale = e.target.value; if (getPref("ui_controller_friendly")) { let timeoutId = e.target.timeoutId; timeoutId && window.clearTimeout(timeoutId), e.target.timeoutId = window.setTimeout(() => { @@ -5234,10 +5185,9 @@ class SettingsNavigationDialog extends NavigationDialog { this.onGlobalSettingChanged(e); }); else if (pref === "user_agent_profile") $control = SettingElement.fromPref("user_agent_profile", STORAGE.Global, (e) => { - const value = e.target.value; - let isCustom = value === "custom", userAgent2 = UserAgent.get(value); + let value = e.target.value, isCustom = value === "custom", userAgent2 = UserAgent.get(value); UserAgent.updateStorage(value); - const $inp = $control.nextElementSibling; + let $inp = $control.nextElementSibling; $inp.value = userAgent2, $inp.readOnly = !isCustom, $inp.disabled = !isCustom, !e.target.disabled && this.onGlobalSettingChanged(e); }); else { @@ -5251,8 +5201,7 @@ class SettingsNavigationDialog extends NavigationDialog { let prefDefinition = null; if (pref) prefDefinition = getPrefDefinition(pref); if (prefDefinition && !this.isSupportedVariant(prefDefinition.requiredVariants)) return; - let label = prefDefinition?.label || setting.label, note = prefDefinition?.note || setting.note, unsupportedNote = prefDefinition?.unsupportedNote || setting.unsupportedNote; - const experimental = prefDefinition?.experimental || setting.experimental; + let label = prefDefinition?.label || setting.label, note = prefDefinition?.note || setting.note, unsupportedNote = prefDefinition?.unsupportedNote || setting.unsupportedNote, experimental = prefDefinition?.experimental || setting.experimental; if (settingTabContent.label && setting.pref) { if (prefDefinition?.suggest) typeof prefDefinition.suggest.lowest !== "undefined" && (this.suggestedSettings.lowest[setting.pref] = prefDefinition.suggest.lowest), typeof prefDefinition.suggest.highest !== "undefined" && (this.suggestedSettings.highest[setting.pref] = prefDefinition.suggest.highest); } @@ -5261,8 +5210,7 @@ class SettingsNavigationDialog extends NavigationDialog { let $note; if (unsupportedNote) $note = CE("div", { class: "bx-settings-dialog-note" }, unsupportedNote); else if (note) $note = CE("div", { class: "bx-settings-dialog-note" }, note); - let $label; - const $row = CE("label", { + let $label, $row = CE("label", { class: "bx-settings-row", for: `bx_setting_${pref}`, "data-type": settingTabContent.group, @@ -5276,8 +5224,7 @@ class SettingsNavigationDialog extends NavigationDialog { $tabContent.appendChild($row), !prefDefinition?.unsupported && setting.onCreated && setting.onCreated(setting, $control); } setupDialog() { - let $tabs, $settings; - const $container = CE("div", { + let $tabs, $settings, $container = CE("div", { class: "bx-settings-dialog", _nearby: { orientation: "horizontal" @@ -5329,9 +5276,9 @@ class SettingsNavigationDialog extends NavigationDialog { if (!settingTab) continue; if (!this.isSupportedVariant(settingTab.requiredVariants)) continue; if (settingTab.group !== "global" && !this.renderFullSettings) continue; - const $svg = this.renderTab(settingTab); + let $svg = this.renderTab(settingTab); $tabs.appendChild($svg); - const $tabContent = CE("div", { + let $tabContent = CE("div", { class: "bx-gone", "data-tab-group": settingTab.group }); @@ -5349,7 +5296,7 @@ class SettingsNavigationDialog extends NavigationDialog { }); } if (label) { - const $title = CE("h2", { + let $title = CE("h2", { _nearby: { orientation: "horizontal" } @@ -5362,7 +5309,7 @@ class SettingsNavigationDialog extends NavigationDialog { $tabContent.appendChild($title); } if (settingTabContent.unsupportedNote) { - const $note = CE("b", { class: "bx-note-unsupported" }, settingTabContent.unsupportedNote); + let $note = CE("b", { class: "bx-note-unsupported" }, settingTabContent.unsupportedNote); $tabContent.appendChild($note); } if (settingTabContent.unsupported) continue; @@ -5385,23 +5332,23 @@ class SettingsNavigationDialog extends NavigationDialog { $tabs.firstElementChild.dispatchEvent(new Event("click")); } focusTab(tabId) { - const $tab = this.$container.querySelector(`.bx-settings-tabs svg[data-group=${tabId}]`); + let $tab = this.$container.querySelector(`.bx-settings-tabs svg[data-group=${tabId}]`); $tab && $tab.dispatchEvent(new Event("click")); } focusIfNeeded() { this.jumpToSettingGroup("next"); } focusActiveTab() { - const $currentTab = this.$tabs.querySelector(".bx-active"); + let $currentTab = this.$tabs.querySelector(".bx-active"); return $currentTab && $currentTab.focus(), !0; } focusVisibleSetting(type = "first") { - const controls = Array.from(this.$settings.querySelectorAll("div[data-tab-group]:not(.bx-gone) > *")); + let controls = Array.from(this.$settings.querySelectorAll("div[data-tab-group]:not(.bx-gone) > *")); if (!controls.length) return !1; if (type === "last") controls.reverse(); for (let $control of controls) { if (!($control instanceof HTMLElement)) continue; - const $focusable = this.dialogManager.findFocusableElement($control); + let $focusable = this.dialogManager.findFocusableElement($control); if ($focusable) { if (this.dialogManager.focus($focusable)) return !0; } @@ -5409,7 +5356,7 @@ class SettingsNavigationDialog extends NavigationDialog { return !1; } focusVisibleTab(type = "first") { - const tabs = Array.from(this.$tabs.querySelectorAll("svg:not(.bx-gone)")); + let tabs = Array.from(this.$tabs.querySelectorAll("svg:not(.bx-gone)")); if (!tabs.length) return !1; if (type === "last") tabs.reverse(); for (let $tab of tabs) @@ -5417,14 +5364,12 @@ class SettingsNavigationDialog extends NavigationDialog { return !1; } jumpToSettingGroup(direction) { - const $tabContent = this.$settings.querySelector("div[data-tab-group]:not(.bx-gone)"); + let $tabContent = this.$settings.querySelector("div[data-tab-group]:not(.bx-gone)"); if (!$tabContent) return !1; - let $header; - const $focusing = document.activeElement; + let $header, $focusing = document.activeElement; if (!$focusing || !$tabContent.contains($focusing)) $header = $tabContent.querySelector("h2"); else { - const $parent = $focusing.closest("[data-tab-group] > *"), siblingProperty = direction === "next" ? "nextSibling" : "previousSibling"; - let $tmp = $parent, times = 0; + let $parent = $focusing.closest("[data-tab-group] > *"), siblingProperty = direction === "next" ? "nextSibling" : "previousSibling", $tmp = $parent, times = 0; while (!0) { if (!$tmp) break; if ($tmp.tagName === "H2") { @@ -5497,11 +5442,10 @@ class ControllerShortcut { } static handle(gamepad) { if (!ControllerShortcut.ACTIONS) ControllerShortcut.ACTIONS = ControllerShortcut.getActionsFromStorage(); - const gamepadIndex = gamepad.index, actions = ControllerShortcut.ACTIONS[gamepad.id]; + let gamepadIndex = gamepad.index, actions = ControllerShortcut.ACTIONS[gamepad.id]; if (!actions) return !1; ControllerShortcut.buttonsCache[gamepadIndex] = ControllerShortcut.buttonsStatus[gamepadIndex].slice(0), ControllerShortcut.buttonsStatus[gamepadIndex] = []; - const pressed = []; - let otherButtonPressed = !1; + let pressed = [], otherButtonPressed = !1; return gamepad.buttons.forEach((button, index) => { if (button.pressed && index !== 16) { if (otherButtonPressed = !0, pressed[index] = !0, actions[index] && !ControllerShortcut.buttonsCache[gamepadIndex][index]) setTimeout(() => ControllerShortcut.runAction(actions[index]), 0); @@ -5544,7 +5488,7 @@ class ControllerShortcut { } } static updateAction(profile, button, action) { - const actions = ControllerShortcut.ACTIONS; + let actions = ControllerShortcut.ACTIONS; if (!(profile in actions)) actions[profile] = []; if (!action) action = null; actions[profile][button] = action; @@ -5560,15 +5504,14 @@ class ControllerShortcut { window.localStorage.setItem(ControllerShortcut.STORAGE_KEY, JSON.stringify(ControllerShortcut.ACTIONS)), console.log(ControllerShortcut.ACTIONS); } static updateProfileList(e) { - const { $selectProfile: $select, $container } = ControllerShortcut, $fragment = document.createDocumentFragment(); + let { $selectProfile: $select, $container } = ControllerShortcut, $fragment = document.createDocumentFragment(); removeChildElements($select); - const gamepads = navigator.getGamepads(); - let hasGamepad = !1; + let gamepads = navigator.getGamepads(), hasGamepad = !1; for (let gamepad of gamepads) { if (!gamepad || !gamepad.connected) continue; if (gamepad.id === VIRTUAL_GAMEPAD_ID) continue; hasGamepad = !0; - const $option = CE("option", { value: gamepad.id }, gamepad.id); + let $option = CE("option", { value: gamepad.id }, gamepad.id); $fragment.appendChild($option); } if ($container.dataset.hasGamepad = hasGamepad.toString(), hasGamepad) $select.appendChild($fragment), $select.selectedIndex = 0, $select.dispatchEvent(new Event("input")); @@ -5578,7 +5521,7 @@ class ControllerShortcut { if (!actions) actions = []; let button; for (button in ControllerShortcut.$selectActions) { - const $select = ControllerShortcut.$selectActions[button]; + let $select = ControllerShortcut.$selectActions[button]; $select.value = actions[button] || "", BxEvent.dispatch($select, "input", { ignoreOnChange: !0, manualTrigger: !0 @@ -5589,11 +5532,11 @@ class ControllerShortcut { return JSON.parse(window.localStorage.getItem(ControllerShortcut.STORAGE_KEY) || "{}"); } static renderSettings() { - const PREF_CONTROLLER_FRIENDLY_UI = getPref("ui_controller_friendly"); + let PREF_CONTROLLER_FRIENDLY_UI = getPref("ui_controller_friendly"); ControllerShortcut.ACTIONS = ControllerShortcut.getActionsFromStorage(); - const buttons = new Map; + let buttons = new Map; buttons.set(3, "⇑"), buttons.set(0, "⇓"), buttons.set(1, "⇒"), buttons.set(2, "⇐"), buttons.set(12, "≻"), buttons.set(13, "≽"), buttons.set(14, "≺"), buttons.set(15, "≼"), buttons.set(8, "⇺"), buttons.set(9, "⇻"), buttons.set(4, "↘"), buttons.set(5, "↙"), buttons.set(6, "↖"), buttons.set(7, "↗"), buttons.set(10, "↺"), buttons.set(11, "↻"); - const actions = { + let actions = { [t("better-xcloud")]: { "bx-settings-show": [t("settings"), t("show")] }, @@ -5615,22 +5558,21 @@ class ControllerShortcut { } }, $baseSelect = CE("select", { autocomplete: "off" }, CE("option", { value: "" }, "---")); for (let groupLabel in actions) { - const items = actions[groupLabel]; + let items = actions[groupLabel]; if (!items) continue; - const $optGroup = CE("optgroup", { label: groupLabel }); + let $optGroup = CE("optgroup", { label: groupLabel }); for (let action in items) { let label = items[action]; if (!label) continue; if (Array.isArray(label)) label = label.join(" ❯ "); - const $option = CE("option", { value: action }, label); + let $option = CE("option", { value: action }, label); $optGroup.appendChild($option); } $baseSelect.appendChild($optGroup); } - let $remap; - const $selectProfile = CE("select", { class: "bx-shortcut-profile", autocomplete: "off" }), $profile = PREF_CONTROLLER_FRIENDLY_UI ? BxSelectElement.wrap($selectProfile) : $selectProfile; + let $remap, $selectProfile = CE("select", { class: "bx-shortcut-profile", autocomplete: "off" }), $profile = PREF_CONTROLLER_FRIENDLY_UI ? BxSelectElement.wrap($selectProfile) : $selectProfile; $profile.classList.add("bx-full-width"); - const $container = CE("div", { + let $container = CE("div", { "data-has-gamepad": "false", _nearby: { focus: $profile @@ -5643,13 +5585,12 @@ class ControllerShortcut { $selectProfile.addEventListener("input", (e) => { ControllerShortcut.switchProfile($selectProfile.value); }); - const onActionChanged = (e) => { - const $target = e.target, profile = $selectProfile.value, button = $target.dataset.button, action = $target.value; + let onActionChanged = (e) => { + let $target = e.target, profile = $selectProfile.value, button = $target.dataset.button, action = $target.value; if (!PREF_CONTROLLER_FRIENDLY_UI) { - const $fakeSelect = $target.previousElementSibling; - let fakeText = "---"; + let $fakeSelect = $target.previousElementSibling, fakeText = "---"; if (action) { - const $selectedOption = $target.options[$target.selectedIndex]; + let $selectedOption = $target.options[$target.selectedIndex]; fakeText = $selectedOption.parentElement.label + " ❯ " + $selectedOption.text; } $fakeSelect.firstElementChild.text = fakeText; @@ -5657,16 +5598,16 @@ class ControllerShortcut { !e.ignoreOnChange && ControllerShortcut.updateAction(profile, button, action); }; for (let [button, prompt2] of buttons) { - const $row = CE("div", { + let $row = CE("div", { class: "bx-shortcut-row" }), $label = CE("label", { class: "bx-prompt" }, `${""} + ${prompt2}`), $div = CE("div", { class: "bx-shortcut-actions" }); if (!PREF_CONTROLLER_FRIENDLY_UI) { - const $fakeSelect = CE("select", { autocomplete: "off" }, CE("option", {}, "---")); + let $fakeSelect = CE("select", { autocomplete: "off" }, CE("option", {}, "---")); $div.appendChild($fakeSelect); } - const $select = $baseSelect.cloneNode(!0); + let $select = $baseSelect.cloneNode(!0); if ($select.dataset.button = button.toString(), $select.addEventListener("input", onActionChanged), ControllerShortcut.$selectActions[button] = $select, PREF_CONTROLLER_FRIENDLY_UI) { - const $bxSelect = BxSelectElement.wrap($select); + let $bxSelect = BxSelectElement.wrap($select); $bxSelect.classList.add("bx-full-width"), $div.appendChild($bxSelect), setNearby($row, { focus: $bxSelect }); @@ -5688,8 +5629,7 @@ var BxExposed = { if (titleInfo.details.hasMkbSupport = supportedInputTypes.includes("MKB"), STATES.userAgent.capabilities.touch) { let touchControllerAvailability = getPref("stream_touch_controller"); if (touchControllerAvailability !== "off" && getPref("stream_touch_controller_auto_off")) { - const gamepads = window.navigator.getGamepads(); - let gamepadFound = !1; + let gamepads = window.navigator.getGamepads(), gamepadFound = !1; for (let gamepad of gamepads) if (gamepad && gamepad.connected) { gamepadFound = !0; @@ -5710,7 +5650,7 @@ var BxExposed = { $media.muted = !0; }); try { - const audioCtx = STATES.currentStream.audioContext, source = audioCtx.createMediaStreamSource(audioStream), gainNode = audioCtx.createGain(); + let audioCtx = STATES.currentStream.audioContext, source = audioCtx.createMediaStreamSource(audioStream), gainNode = audioCtx.createGain(); source.connect(gainNode).connect(audioCtx.destination); } catch (e) { BxLogger.error("setupGainNode", e), STATES.currentStream.audioGainNode = null; @@ -5725,9 +5665,9 @@ var BxExposed = { }, disableGamepadPolling: !1, backButtonPressed: () => { - const navigationDialogManager = NavigationDialogManager.getInstance(); + let navigationDialogManager = NavigationDialogManager.getInstance(); if (navigationDialogManager.isShowing()) return navigationDialogManager.hide(), !0; - const dict = { + let dict = { bubbles: !0, cancelable: !0, key: "XF86Back", @@ -5739,9 +5679,9 @@ var BxExposed = { } }; function localRedirect(path) { - const url = window.location.href.substring(0, 31) + path, $pageContent = document.getElementById("PageContent"); + let url = window.location.href.substring(0, 31) + path, $pageContent = document.getElementById("PageContent"); if (!$pageContent) return; - const $anchor = CE("a", { + let $anchor = CE("a", { href: url, class: "bx-hidden bx-offscreen" }, ""); @@ -5757,7 +5697,7 @@ function getPreferredServerRegion(shortName = !1) { if (preferredRegion in STATES.serverRegions) if (shortName && STATES.serverRegions[preferredRegion].shortName) return STATES.serverRegions[preferredRegion].shortName; else return preferredRegion; for (let regionName in STATES.serverRegions) { - const region = STATES.serverRegions[regionName]; + let region = STATES.serverRegions[regionName]; if (!region.isDefault) continue; if (shortName && region.shortName) return region.shortName; else return regionName; @@ -5787,7 +5727,7 @@ class HeaderSection { static #timeout; static #injectSettingsButton($parent) { if (!$parent) return; - const PREF_LATEST_VERSION = getPref("version_latest"), $btnSettings = HeaderSection.#$settingsBtn; + let PREF_LATEST_VERSION = getPref("version_latest"), $btnSettings = HeaderSection.#$settingsBtn; if (isElementVisible(HeaderSection.#$buttonsWrapper)) return; if ($btnSettings.querySelector("span").textContent = getPreferredServerRegion(!0) || t("better-xcloud"), !SCRIPT_VERSION.includes("beta") && PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) $btnSettings.setAttribute("data-update-available", "true"); $parent.appendChild(HeaderSection.#$buttonsWrapper); @@ -5801,7 +5741,7 @@ class HeaderSection { HeaderSection.#$remotePlayBtn.classList.remove("bx-gone"); } static watchHeader() { - const $root = document.querySelector("#PageContent header") || document.querySelector("#root"); + let $root = document.querySelector("#PageContent header") || document.querySelector("#root"); if (!$root) return; HeaderSection.#timeout && clearTimeout(HeaderSection.#timeout), HeaderSection.#timeout = null, HeaderSection.#observer && HeaderSection.#observer.disconnect(), HeaderSection.#observer = new MutationObserver((mutationList) => { HeaderSection.#timeout && clearTimeout(HeaderSection.#timeout), HeaderSection.#timeout = window.setTimeout(HeaderSection.checkHeader, 2000); @@ -5823,22 +5763,21 @@ class RemotePlayNavigationDialog extends NavigationDialog { this.setupDialog(); } setupDialog() { - const $fragment = CE("div", { class: "bx-remote-play-container" }), $settingNote = CE("p", {}), currentResolution = getPref("xhome_resolution"); - let $resolutions = CE("select", {}, CE("option", { value: "1080p" }, "1080p"), CE("option", { value: "720p" }, "720p")); + let $fragment = CE("div", { class: "bx-remote-play-container" }), $settingNote = CE("p", {}), currentResolution = getPref("xhome_resolution"), $resolutions = CE("select", {}, CE("option", { value: "1080p" }, "1080p"), CE("option", { value: "720p" }, "720p")); if (getPref("ui_controller_friendly")) $resolutions = BxSelectElement.wrap($resolutions); $resolutions.addEventListener("input", (e) => { - const value = e.target.value; + let value = e.target.value; $settingNote.textContent = value === "1080p" ? "✅ " + t("can-stream-xbox-360-games") : "❌ " + t("cant-stream-xbox-360-games"), setPref("xhome_resolution", value); }), $resolutions.value = currentResolution, BxEvent.dispatch($resolutions, "input", { manualTrigger: !0 }); - const $qualitySettings = CE("div", { + let $qualitySettings = CE("div", { class: "bx-remote-play-settings" }, CE("div", {}, CE("label", {}, t("target-resolution"), $settingNote), $resolutions)); $fragment.appendChild($qualitySettings); - const manager = RemotePlayManager.getInstance(), consoles = manager.getConsoles(); + let manager = RemotePlayManager.getInstance(), consoles = manager.getConsoles(); for (let con of consoles) { - const $child = CE("div", { class: "bx-remote-play-device-wrapper" }, CE("div", { class: "bx-remote-play-device-info" }, CE("div", {}, CE("span", { class: "bx-remote-play-device-name" }, con.deviceName), CE("span", { class: "bx-remote-play-console-type" }, con.consoleType.replace("Xbox", ""))), CE("div", { class: "bx-remote-play-power-state" }, this.STATE_LABELS[con.powerState])), createButton({ + let $child = CE("div", { class: "bx-remote-play-device-wrapper" }, CE("div", { class: "bx-remote-play-device-info" }, CE("div", {}, CE("span", { class: "bx-remote-play-device-name" }, con.deviceName), CE("span", { class: "bx-remote-play-console-type" }, con.consoleType.replace("Xbox", ""))), CE("div", { class: "bx-remote-play-power-state" }, this.STATE_LABELS[con.powerState])), createButton({ classes: ["bx-remote-play-connect-button"], label: t("console-connect"), style: 1 | 32, @@ -5869,7 +5808,7 @@ class RemotePlayNavigationDialog extends NavigationDialog { return this.$container; } focusIfNeeded() { - const $btnConnect = this.$container.querySelector(".bx-remote-play-device-wrapper button"); + let $btnConnect = this.$container.querySelector(".bx-remote-play-device-wrapper button"); $btnConnect && $btnConnect.focus(); } } @@ -5912,9 +5851,9 @@ class RemotePlayManager { GSSV_TOKEN = JSON.parse(localStorage.getItem("xboxcom_xbl_user_info")).tokens["http://gssv.xboxlive.com/"].token; } catch (e) { for (let i = 0;i < localStorage.length; i++) { - const key = localStorage.key(i); + let key = localStorage.key(i); if (!key.startsWith("Auth.User.")) continue; - const json = JSON.parse(localStorage.getItem(key)); + let json = JSON.parse(localStorage.getItem(key)); for (let token of json.tokens) { if (!token.relyingParty.includes("gssv.xboxlive.com")) continue; GSSV_TOKEN = token.tokenData.token; @@ -5923,7 +5862,7 @@ class RemotePlayManager { break; } } - const request = new Request("https://xhome.gssv-play-prod.xboxlive.com/v2/login/user", { + let request = new Request("https://xhome.gssv-play-prod.xboxlive.com/v2/login/user", { method: "POST", body: JSON.stringify({ offeringId: "xhome", @@ -5942,7 +5881,7 @@ class RemotePlayManager { callback(); return; } - const options = { + let options = { method: "GET", headers: { Authorization: `Bearer ${this.XHOME_TOKEN}` @@ -5950,7 +5889,7 @@ class RemotePlayManager { }; for (let region of this.regions) try { - const request = new Request(`${region.baseUri}/v6/servers/home?mr=50`, options), json = await (await fetch(request)).json(); + let request = new Request(`${region.baseUri}/v6/servers/home?mr=50`, options), json = await (await fetch(request)).json(); if (json.results.length === 0) continue; this.consoles = json.results, STATES.remotePlay.server = region.baseUri; break; @@ -6030,7 +5969,7 @@ class XhomeInterceptor { }; static async handleLogin(request) { try { - const obj = await request.clone().json(); + let obj = await request.clone().json(); obj.offeringId = "xhome", request = new Request("https://xhome.gssv-play-prod.xboxlive.com/v2/login/user", { method: "POST", body: JSON.stringify(obj), @@ -6044,10 +5983,10 @@ class XhomeInterceptor { return NATIVE_FETCH(request); } static async handleConfiguration(request) { - const response = await NATIVE_FETCH(request), obj = await response.clone().json(); + let response = await NATIVE_FETCH(request), obj = await response.clone().json(); console.log(obj); - const processPorts = (port) => { - const ports = new Set; + let processPorts = (port) => { + let ports = new Set; return port && ports.add(port), ports.add(9002), Array.from(ports); }, serverDetails = obj.serverDetails; if (serverDetails.ipAddress) XhomeInterceptor.consoleAddrs[serverDetails.ipAddress] = processPorts(serverDetails.port); @@ -6056,14 +5995,13 @@ class XhomeInterceptor { return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response; } static async handleInputConfigs(request, opts) { - const response = await NATIVE_FETCH(request); + let response = await NATIVE_FETCH(request); if (getPref("stream_touch_controller") !== "all") return response; - const obj = await response.clone().json(), xboxTitleId = JSON.parse(opts.body).titleIds[0]; + let obj = await response.clone().json(), xboxTitleId = JSON.parse(opts.body).titleIds[0]; TouchController.setXboxTitleId(xboxTitleId); - const inputConfigs = obj[0]; - let hasTouchSupport = inputConfigs.supportedTabs.length > 0; + let inputConfigs = obj[0], hasTouchSupport = inputConfigs.supportedTabs.length > 0; if (!hasTouchSupport) { - const supportedInputTypes = inputConfigs.supportedInputTypes; + let supportedInputTypes = inputConfigs.supportedInputTypes; hasTouchSupport = supportedInputTypes.includes("NativeTouch") || supportedInputTypes.includes("CustomTouchOverlay"); } if (hasTouchSupport) TouchController.disable(), BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, { @@ -6073,11 +6011,11 @@ class XhomeInterceptor { return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response; } static async handleTitles(request) { - const clone = request.clone(), headers = {}; + let clone = request.clone(), headers = {}; for (let pair of clone.headers.entries()) headers[pair[0]] = pair[1]; headers.authorization = `Bearer ${RemotePlayManager.getInstance().xcloudToken}`; - const index = request.url.indexOf(".xboxlive.com"); + let index = request.url.indexOf(".xboxlive.com"); return request = new Request("https://wus.core.gssv-play-prod" + request.url.substring(index), { method: clone.method, body: await clone.text(), @@ -6085,28 +6023,28 @@ class XhomeInterceptor { }), NATIVE_FETCH(request); } static async handlePlay(request) { - const body = await request.clone().json(), newRequest = new Request(request, { + let body = await request.clone().json(), newRequest = new Request(request, { body: JSON.stringify(body) }); return NATIVE_FETCH(newRequest); } static async handle(request) { TouchController.disable(); - const clone = request.clone(), headers = {}; + let clone = request.clone(), headers = {}; for (let pair of clone.headers.entries()) headers[pair[0]] = pair[1]; headers.authorization = `Bearer ${RemotePlayManager.getInstance().xhomeToken}`; - const deviceInfo = XhomeInterceptor.BASE_DEVICE_INFO; + let deviceInfo = XhomeInterceptor.BASE_DEVICE_INFO; if (getPref("xhome_resolution") === "720p") deviceInfo.dev.os.name = "android"; headers["x-ms-device-info"] = JSON.stringify(deviceInfo); - const opts = { + let opts = { method: clone.method, headers }; if (clone.method === "POST") opts.body = await clone.text(); let newUrl = request.url; if (!newUrl.includes("/servers/home")) { - const index = request.url.indexOf(".xboxlive.com"); + let index = request.url.indexOf(".xboxlive.com"); newUrl = STATES.remotePlay.server + request.url.substring(index + 13); } request = new Request(newUrl, opts); @@ -6126,14 +6064,14 @@ class LoadingScreen { static waitTimeInterval = null; static orgWebTitle; static secondsToString(seconds) { - const m = Math.floor(seconds / 60), s = Math.floor(seconds % 60), mDisplay = m > 0 ? `${m}m` : "", sDisplay = `${s}s`.padStart(s >= 0 ? 3 : 4, "0"); + let m = Math.floor(seconds / 60), s = Math.floor(seconds % 60), mDisplay = m > 0 ? `${m}m` : "", sDisplay = `${s}s`.padStart(s >= 0 ? 3 : 4, "0"); return mDisplay + sDisplay; } static setup() { - const titleInfo = STATES.currentStream.titleInfo; + let titleInfo = STATES.currentStream.titleInfo; if (!titleInfo) return; if (!LoadingScreen.$bgStyle) { - const $bgStyle = CE("style"); + let $bgStyle = CE("style"); document.documentElement.appendChild($bgStyle), LoadingScreen.$bgStyle = $bgStyle; } if (LoadingScreen.setBackground(titleInfo.product.heroImageUrl || titleInfo.product.titledHeroImageUrl || titleInfo.product.tileImageUrl), getPref("ui_loading_screen_rocket") === "hide") LoadingScreen.hideRocket(); @@ -6145,7 +6083,7 @@ class LoadingScreen { static setBackground(imageUrl) { let $bgStyle = LoadingScreen.$bgStyle; imageUrl = imageUrl + "?w=1920", $bgStyle.textContent += '#game-stream{background-color:transparent !important;background-position:center center !important;background-repeat:no-repeat !important;background-size:cover !important}#game-stream rect[width="800"]{transition:opacity .3s ease-in-out !important}' + `#game-stream {background-image: linear-gradient(#00000033, #000000e6), url(${imageUrl}) !important;}`; - const bg = new Image; + let bg = new Image; bg.onload = (e) => { $bgStyle.textContent += '#game-stream rect[width="800"]{opacity:0 !important}'; }, bg.src = imageUrl; @@ -6154,7 +6092,7 @@ class LoadingScreen { if (getPref("ui_loading_screen_rocket") === "hide-queue") LoadingScreen.hideRocket(); let secondsLeft = waitTime, $countDown, $estimated; LoadingScreen.orgWebTitle = document.title; - const endDate = new Date, timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60; + let endDate = new Date, timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60; endDate.setSeconds(endDate.getSeconds() + waitTime - timeZoneOffsetSeconds); let endDateStr = endDate.toISOString().slice(0, 19); endDateStr = endDateStr.substring(0, 10) + " " + endDateStr.substring(11, 19), endDateStr += ` (${LoadingScreen.secondsToString(waitTime)})`; @@ -6167,7 +6105,7 @@ class LoadingScreen { } static hide() { if (LoadingScreen.orgWebTitle && (document.title = LoadingScreen.orgWebTitle), LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add("bx-gone"), getPref("ui_loading_screen_game_art") && LoadingScreen.$bgStyle) { - const $rocketBg = document.querySelector('#game-stream rect[width="800"]'); + let $rocketBg = document.querySelector('#game-stream rect[width="800"]'); $rocketBg && $rocketBg.addEventListener("transitionend", (e) => { LoadingScreen.$bgStyle.textContent += "#game-stream{background:#000 !important}"; }), LoadingScreen.$bgStyle.textContent += '#game-stream rect[width="800"]{opacity:1 !important}'; @@ -6195,25 +6133,24 @@ class TrueAchievements { }); static onClick(e) { e.preventDefault(); - const dataset = TrueAchievements.$link.dataset; + let dataset = TrueAchievements.$link.dataset; TrueAchievements.open(!0, dataset.xboxTitleId, dataset.id), window.BX_EXPOSED.dialogRoutes?.closeAll(); } static $hiddenLink = CE("a", { target: "_blank" }); static updateIds(xboxTitleId, id2) { - const { $link, $button } = TrueAchievements; + let { $link, $button } = TrueAchievements; if (clearDataSet($link), clearDataSet($button), xboxTitleId) $link.dataset.xboxTitleId = xboxTitleId, $button.dataset.xboxTitleId = xboxTitleId; if (id2) $link.dataset.id = id2, $button.dataset.id = id2; } static injectAchievementsProgress($elm) { if (SCRIPT_VARIANT !== "full") return; - const $parent = $elm.parentElement, $div = CE("div", { + let $parent = $elm.parentElement, $div = CE("div", { class: "bx-guide-home-achievements-progress" - }, $elm); - let xboxTitleId; + }, $elm), xboxTitleId; try { - const $container = $parent.closest("div[class*=AchievementsPreview-module__container]"); + let $container = $parent.closest("div[class*=AchievementsPreview-module__container]"); if ($container) xboxTitleId = getReactProps($container).children.props.data.data.xboxTitleId; } catch (e) {} if (!xboxTitleId) xboxTitleId = TrueAchievements.getStreamXboxTitleId(); @@ -6224,11 +6161,10 @@ class TrueAchievements { } static injectAchievementDetailPage($parent) { if (SCRIPT_VARIANT !== "full") return; - const props = getReactProps($parent); + let props = getReactProps($parent); if (!props) return; try { - const achievementList = props.children.props.data.data, $header = $parent.querySelector("div[class*=AchievementDetailHeader]"), achievementName = getReactProps($header).children[0].props.achievementName; - let id2, xboxTitleId; + let achievementList = props.children.props.data.data, $header = $parent.querySelector("div[class*=AchievementDetailHeader]"), achievementName = getReactProps($header).children[0].props.achievementName, id2, xboxTitleId; for (let achiev of achievementList) if (achiev.name === achievementName) { id2 = achiev.id, xboxTitleId = achiev.title.id; @@ -6306,12 +6242,12 @@ class GuideMenu { window.BX_EXPOSED.dialogRoutes.closeAll(); return; } - const $btnClose = document.querySelector("#gamepass-dialog-root button[class^=Header-module__closeButton]"); + let $btnClose = document.querySelector("#gamepass-dialog-root button[class^=Header-module__closeButton]"); $btnClose && $btnClose.click(); } static #renderButtons() { if (GuideMenu.#$renderedButtons) return GuideMenu.#$renderedButtons; - const $div = CE("div", { + let $div = CE("div", { class: "bx-guide-home-buttons" }), buttons = [ GuideMenu.#BUTTONS.scriptSettings, @@ -6325,7 +6261,7 @@ class GuideMenu { if (!$button) continue; if ($button instanceof HTMLElement) $div.appendChild($button); else if (Array.isArray($button)) { - const $wrapper = CE("div", {}); + let $wrapper = CE("div", {}); for (let $child of $button) $child && $wrapper.appendChild($child); $div.appendChild($wrapper); @@ -6335,25 +6271,25 @@ class GuideMenu { } static #injectHome($root, isPlaying = !1) { { - const $achievementsProgress = $root.querySelector("button[class*=AchievementsButton-module__progressBarContainer]"); + let $achievementsProgress = $root.querySelector("button[class*=AchievementsButton-module__progressBarContainer]"); if ($achievementsProgress) TrueAchievements.injectAchievementsProgress($achievementsProgress); } let $target = null; if (isPlaying) { $target = $root.querySelector("a[class*=QuitGameButton]"); - const $btnXcloudHome = $root.querySelector("div[class^=HomeButtonWithDivider]"); + let $btnXcloudHome = $root.querySelector("div[class^=HomeButtonWithDivider]"); $btnXcloudHome && ($btnXcloudHome.style.display = "none"); } else { - const $dividers = $root.querySelectorAll("div[class*=Divider-module__divider]"); + let $dividers = $root.querySelectorAll("div[class*=Divider-module__divider]"); if ($dividers) $target = $dividers[$dividers.length - 1]; } if (!$target) return !1; - const $buttons = GuideMenu.#renderButtons(); + let $buttons = GuideMenu.#renderButtons(); $buttons.dataset.isPlaying = isPlaying.toString(), $target.insertAdjacentElement("afterend", $buttons); } static async#onShown(e) { if (e.where === "home") { - const $root = document.querySelector("#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]"); + let $root = document.querySelector("#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]"); $root && GuideMenu.#injectHome($root, STATES.isPlaying); } } @@ -6361,20 +6297,20 @@ class GuideMenu { window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, GuideMenu.#onShown); } static observe($addedElm) { - const className = $addedElm.className; + let className = $addedElm.className; if (className.includes("AchievementsButton-module__progressBarContainer")) { TrueAchievements.injectAchievementsProgress($addedElm); return; } if (!className.startsWith("NavigationAnimation") && !className.startsWith("DialogRoutes") && !className.startsWith("Dialog-module__container")) return; { - const $achievDetailPage = $addedElm.querySelector("div[class*=AchievementDetailPage]"); + let $achievDetailPage = $addedElm.querySelector("div[class*=AchievementDetailPage]"); if ($achievDetailPage) { TrueAchievements.injectAchievementDetailPage($achievDetailPage); return; } } - const $selectedTab = $addedElm.querySelector("div[class^=NavigationMenu] button[aria-selected=true"); + let $selectedTab = $addedElm.querySelector("div[class^=NavigationMenu] button[aria-selected=true"); if ($selectedTab) { let $elm = $selectedTab, index; for (index = 0;$elm = $elm?.previousElementSibling; index++) @@ -6434,8 +6370,7 @@ class StreamBadges { }; } renderBadge(name, value) { - const badgeInfo = this.badges[name]; - let $badge; + let badgeInfo = this.badges[name], $badge; if (badgeInfo.$element) return $badge = badgeInfo.$element, $badge.lastElementChild.textContent = value, $badge; if ($badge = CE("div", { class: "bx-badge", title: badgeInfo.name }, CE("span", { class: "bx-badge-name" }, createSvgIcon(badgeInfo.icon)), CE("span", { class: "bx-badge-value", style: `background-color: ${badgeInfo.color}` }, value)), name === "battery") $badge.classList.add("bx-badge-battery"); return this.badges[name].$element = $badge, $badge; @@ -6445,19 +6380,18 @@ class StreamBadges { this.stop(); return; } - const statsCollector = StreamStatsCollector.getInstance(); + let statsCollector = StreamStatsCollector.getInstance(); await statsCollector.collect(); - const play = statsCollector.getStat("play"), batt = statsCollector.getStat("batt"), dl = statsCollector.getStat("dl"), ul = statsCollector.getStat("ul"), badges = { + let play = statsCollector.getStat("play"), batt = statsCollector.getStat("batt"), dl = statsCollector.getStat("dl"), ul = statsCollector.getStat("ul"), badges = { download: dl.toString(), upload: ul.toString(), playtime: play.toString(), battery: batt.toString() - }; - let name; + }, name; for (name in badges) { - const value = badges[name]; + let value = badges[name]; if (value === null) continue; - const $elm = this.badges[name].$element; + let $elm = this.badges[name].$element; if (!$elm) continue; if ($elm.lastElementChild.textContent = value, name === "battery") if (batt.current === 100 && batt.start === 100) $elm.classList.add("bx-gone"); else $elm.dataset.charging = batt.isCharging.toString(), $elm.classList.remove("bx-gone"); @@ -6474,7 +6408,7 @@ class StreamBadges { await this.getServerStats(); let batteryLevel = ""; if (STATES.browser.capabilities.batteryApi) batteryLevel = "100%"; - const BADGES = [ + let BADGES = [ ["playtime", "1m"], ["battery", batteryLevel], ["download", humanFileSize(0)], @@ -6492,15 +6426,10 @@ class StreamBadges { }), this.$container = $container, await this.start(), $container; } async getServerStats() { - const stats = await STATES.currentStream.peerConnection.getStats(), allVideoCodecs = {}; - let videoCodecId, videoWidth = 0, videoHeight = 0; - const allAudioCodecs = {}; - let audioCodecId; - const allCandidates = {}; - let candidateId; + let stats = await STATES.currentStream.peerConnection.getStats(), allVideoCodecs = {}, videoCodecId, videoWidth = 0, videoHeight = 0, allAudioCodecs = {}, audioCodecId, allCandidates = {}, candidateId; if (stats.forEach((stat) => { if (stat.type === "codec") { - const mimeType = stat.mimeType.split("/")[0]; + let mimeType = stat.mimeType.split("/")[0]; if (mimeType === "video") allVideoCodecs[stat.id] = stat; else if (mimeType === "audio") allAudioCodecs[stat.id] = stat; } else if (stat.type === "inbound-rtp" && stat.packetsReceived > 0) { @@ -6509,19 +6438,18 @@ class StreamBadges { } else if (stat.type === "candidate-pair" && stat.packetsReceived > 0 && stat.state === "succeeded") candidateId = stat.remoteCandidateId; else if (stat.type === "remote-candidate") allCandidates[stat.id] = stat.address; }), videoCodecId) { - const videoStat = allVideoCodecs[videoCodecId], video = { + let videoStat = allVideoCodecs[videoCodecId], video = { width: videoWidth, height: videoHeight, codec: videoStat.mimeType.substring(6) }; if (video.codec === "H264") { - const match = /profile-level-id=([0-9a-f]{6})/.exec(videoStat.sdpFmtpLine); + let match = /profile-level-id=([0-9a-f]{6})/.exec(videoStat.sdpFmtpLine); match && (video.profile = match[1]); } let text = videoHeight + "p"; if (text && (text += "/"), text += video.codec, video.profile) { - const profile = video.profile; - let quality = profile; + let profile = video.profile, quality = profile; if (profile.startsWith("4d")) quality = t("visual-quality-high"); else if (profile.startsWith("42e")) quality = t("visual-quality-normal"); else if (profile.startsWith("420")) quality = t("visual-quality-low"); @@ -6530,7 +6458,7 @@ class StreamBadges { this.badges.video.$element = this.renderBadge("video", text), this.serverInfo.video = video; } if (audioCodecId) { - const audioStat = allAudioCodecs[audioCodecId], audio = { + let audioStat = allAudioCodecs[audioCodecId], audio = { codec: audioStat.mimeType.substring(6), bitrate: audioStat.clockRate }, bitrate = audio.bitrate / 1000, text = `${audio.codec} (${bitrate} kHz)`; @@ -6538,7 +6466,7 @@ class StreamBadges { } if (candidateId) { BxLogger.info("candidate", candidateId, allCandidates); - const server = this.serverInfo.server; + let server = this.serverInfo.server; if (server) { server.ipv6 = allCandidates[candidateId].includes(":"); let text = ""; @@ -6567,19 +6495,18 @@ class XcloudInterceptor { WestUS2: "🇺🇸" }; static async handleLogin(request, init) { - const bypassServer = getPref("server_bypass_restriction"); + let bypassServer = getPref("server_bypass_restriction"); if (bypassServer !== "off") { - const ip = BypassServerIps[bypassServer]; + let ip = BypassServerIps[bypassServer]; ip && request.headers.set("X-Forwarded-For", ip); } - const response = await NATIVE_FETCH(request, init); + let response = await NATIVE_FETCH(request, init); if (response.status !== 200) return BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE), response; - const obj = await response.clone().json(); + let obj = await response.clone().json(); RemotePlayManager.getInstance().xcloudToken = obj.gsToken; - const serverRegex = /\/\/(\w+)\./, serverEmojis = XcloudInterceptor.SERVER_EMOJIS; + let serverRegex = /\/\/(\w+)\./, serverEmojis = XcloudInterceptor.SERVER_EMOJIS; for (let region of obj.offeringSettings.regions) { - const regionName = region.name; - let shortName = region.name; + let { name: regionName, name: shortName } = region; if (region.isDefault) STATES.selectedRegion = Object.assign({}, region); let match = serverRegex.exec(region.baseUri); if (match) { @@ -6588,39 +6515,38 @@ class XcloudInterceptor { region.shortName = shortName.toUpperCase(), STATES.serverRegions[region.name] = Object.assign({}, region); } BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_READY); - const preferredRegion = getPreferredServerRegion(); + let preferredRegion = getPreferredServerRegion(); if (preferredRegion && preferredRegion in STATES.serverRegions) { - const tmp = Object.assign({}, STATES.serverRegions[preferredRegion]); + let tmp = Object.assign({}, STATES.serverRegions[preferredRegion]); tmp.isDefault = !0, obj.offeringSettings.regions = [tmp], STATES.selectedRegion = tmp; } return STATES.gsToken = obj.gsToken, response.json = () => Promise.resolve(obj), response; } static async handlePlay(request, init) { - const PREF_STREAM_TARGET_RESOLUTION = getPref("stream_target_resolution"), PREF_STREAM_PREFERRED_LOCALE = getPref("stream_preferred_locale"), url = typeof request === "string" ? request : request.url, parsedUrl = new URL(url); - let badgeRegion = parsedUrl.host.split(".", 1)[0]; + let PREF_STREAM_TARGET_RESOLUTION = getPref("stream_target_resolution"), PREF_STREAM_PREFERRED_LOCALE = getPref("stream_preferred_locale"), url = typeof request === "string" ? request : request.url, parsedUrl = new URL(url), badgeRegion = parsedUrl.host.split(".", 1)[0]; for (let regionName in STATES.serverRegions) { - const region = STATES.serverRegions[regionName]; + let region = STATES.serverRegions[regionName]; if (parsedUrl.origin == region.baseUri) { badgeRegion = regionName; break; } } StreamBadges.getInstance().setRegion(badgeRegion); - const body = await request.clone().json(); + let body = await request.clone().json(); if (PREF_STREAM_TARGET_RESOLUTION !== "auto") { - const osName = PREF_STREAM_TARGET_RESOLUTION === "720p" ? "android" : "windows"; + let osName = PREF_STREAM_TARGET_RESOLUTION === "720p" ? "android" : "windows"; body.settings.osName = osName; } if (PREF_STREAM_PREFERRED_LOCALE !== "default") body.settings.locale = PREF_STREAM_PREFERRED_LOCALE; - const newRequest = new Request(request, { + let newRequest = new Request(request, { body: JSON.stringify(body) }); return NATIVE_FETCH(newRequest); } static async handleWaitTime(request, init) { - const response = await NATIVE_FETCH(request, init); + let response = await NATIVE_FETCH(request, init); if (getPref("ui_loading_screen_wait_time")) { - const json = await response.clone().json(); + let json = await response.clone().json(); if (json.estimatedAllocationTimeInSeconds > 0) LoadingScreen.setupWaitTime(json.estimatedTotalWaitTimeInSeconds); } return response; @@ -6629,10 +6555,9 @@ class XcloudInterceptor { if (request.method !== "GET") return NATIVE_FETCH(request, init); if (getPref("stream_touch_controller") === "all") if (STATES.currentStream.titleInfo?.details.hasTouchSupport) TouchController.disable(); else TouchController.enable(); - const response = await NATIVE_FETCH(request, init), text = await response.clone().text(); + let response = await NATIVE_FETCH(request, init), text = await response.clone().text(); if (!text.length) return response; - const obj = JSON.parse(text); - let overrides = JSON.parse(obj.clientStreamingConfigOverrides || "{}") || {}; + let obj = JSON.parse(text), overrides = JSON.parse(obj.clientStreamingConfigOverrides || "{}") || {}; overrides.inputConfiguration = overrides.inputConfiguration || {}, overrides.inputConfiguration.enableVibration = !0; let overrideMkb = null; if (getPref("native_mkb_enabled") === "on" || STATES.currentStream.titleInfo && BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo.details.productId)) overrideMkb = !0; @@ -6659,11 +6584,11 @@ function clearApplicationInsightsBuffers() { window.sessionStorage.removeItem("AI_buffer"), window.sessionStorage.removeItem("AI_sentBuffer"); } function clearDbLogs(dbName, table) { - const request = window.indexedDB.open(dbName); + let request = window.indexedDB.open(dbName); request.onsuccess = (e) => { - const db = e.target.result; + let db = e.target.result; try { - const objectStoreRequest = db.transaction(table, "readwrite").objectStore(table).clear(); + let objectStoreRequest = db.transaction(table, "readwrite").objectStore(table).clear(); objectStoreRequest.onsuccess = function() { console.log(`[Better xCloud] Cleared ${dbName}.${table}`); }; @@ -6674,19 +6599,17 @@ function clearAllLogs() { clearApplicationInsightsBuffers(), clearDbLogs("StreamClientLogHandler", "logs"), clearDbLogs("XCloudAppLogs", "logs"); } function updateIceCandidates(candidates, options) { - const pattern = new RegExp(/a=candidate:(?\d+) (?\d+) UDP (?\d+) (?[^\s]+) (?\d+) (?.*)/), lst = []; + let pattern = new RegExp(/a=candidate:(?\d+) (?\d+) UDP (?\d+) (?[^\s]+) (?\d+) (?.*)/), lst = []; for (let item2 of candidates) { if (item2.candidate == "a=end-of-candidates") continue; - const groups = pattern.exec(item2.candidate).groups; + let groups = pattern.exec(item2.candidate).groups; lst.push(groups); } if (options.preferIpv6Server) lst.sort((a, b) => { - const firstIp = a.ip, secondIp = b.ip; + let firstIp = a.ip, secondIp = b.ip; return !firstIp.includes(":") && secondIp.includes(":") ? 1 : -1; }); - const newCandidates = []; - let foundation = 1; - const newCandidate = (candidate) => { + let newCandidates = [], foundation = 1, newCandidate = (candidate) => { return { candidate, messageType: "iceCandidate", @@ -6703,13 +6626,12 @@ function updateIceCandidates(candidates, options) { return newCandidates.push(newCandidate("a=end-of-candidates")), BxLogger.info("ICE Candidates", newCandidates), newCandidates; } async function patchIceCandidates(request, consoleAddrs) { - const response = await NATIVE_FETCH(request), text = await response.clone().text(); + let response = await NATIVE_FETCH(request), text = await response.clone().text(); if (!text.length) return response; - const options = { + let options = { preferIpv6Server: getPref("prefer_ipv6_server"), consoleAddrs - }, obj = JSON.parse(text); - let exchangeResponse = JSON.parse(obj.exchangeResponse); + }, obj = JSON.parse(text), exchangeResponse = JSON.parse(obj.exchangeResponse); return exchangeResponse = updateIceCandidates(exchangeResponse, options), obj.exchangeResponse = JSON.stringify(exchangeResponse), response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response; } function interceptHttpRequests() { @@ -6725,7 +6647,7 @@ function interceptHttpRequests() { "https://peoplehub.xboxlive.com/users/me/people/recommendations", "https://xblmessaging.xboxlive.com/network/xbox/users/me/inbox" ]); - const xhrPrototype = XMLHttpRequest.prototype, nativeXhrOpen = xhrPrototype.open, nativeXhrSend = xhrPrototype.send; + let xhrPrototype = XMLHttpRequest.prototype, nativeXhrOpen = xhrPrototype.open, nativeXhrSend = xhrPrototype.send; xhrPrototype.open = function(method, url) { return this._url = url, nativeXhrOpen.apply(this, arguments); }, xhrPrototype.send = function(...arg) { @@ -6749,7 +6671,7 @@ function interceptHttpRequests() { if (url.endsWith("/play")) BxEvent.dispatch(window, BxEvent.STREAM_LOADING); if (url.endsWith("/configuration")) BxEvent.dispatch(window, BxEvent.STREAM_STARTING); if (url.startsWith("https://emerald.xboxservices.com/xboxcomfd/experimentation")) try { - const response = await NATIVE_FETCH(request, init), json = await response.json(); + let response = await NATIVE_FETCH(request, init), json = await response.json(); if (json && json.exp && json.exp.treatments) for (let key in FeatureGates) json.exp.treatments[key] = FeatureGates[key]; return response.json = () => Promise.resolve(json), response; @@ -6757,13 +6679,13 @@ function interceptHttpRequests() { console.log(e); } if (STATES.userAgent.capabilities.touch && url.includes("catalog.gamepass.com/sigls/")) { - const response = await NATIVE_FETCH(request, init), obj = await response.clone().json(); + let response = await NATIVE_FETCH(request, init), obj = await response.clone().json(); if (url.includes("29a81209-df6f-41fd-a528-2ae6b91f719c")) for (let i = 1;i < obj.length; i++) gamepassAllGames.push(obj[i].id); else if (url.includes("9c86f07a-f3e8-45ad-82a0-a1f759597059")) try { let customList = TouchController.getCustomList(); customList = customList.filter((id2) => gamepassAllGames.includes(id2)); - const newCustomList = customList.map((item2) => ({ id: item2 })); + let newCustomList = customList.map((item2) => ({ id: item2 })); obj.push(...newCustomList); } catch (e) { console.log(e); @@ -6771,9 +6693,9 @@ function interceptHttpRequests() { return response.json = () => Promise.resolve(obj), response; } if (BX_FLAGS.ForceNativeMkbTitles && url.includes("catalog.gamepass.com/sigls/") && url.includes("8fa264dd-124f-4af3-97e8-596fcdf4b486")) { - const response = await NATIVE_FETCH(request, init), obj = await response.clone().json(); + let response = await NATIVE_FETCH(request, init), obj = await response.clone().json(); try { - const newCustomList = BX_FLAGS.ForceNativeMkbTitles.map((item2) => ({ id: item2 })); + let newCustomList = BX_FLAGS.ForceNativeMkbTitles.map((item2) => ({ id: item2 })); obj.push(...newCustomList); } catch (e) { console.log(e); @@ -6792,7 +6714,7 @@ function showGamepadToast(gamepad) { BxLogger.info("Gamepad", gamepad); let text = "🎮"; if (getPref("local_co_op_enabled")) text += ` #${gamepad.index + 1}`; - const gamepadId = gamepad.id.replace(/ \(.*?Vendor: \w+ Product: \w+\)$/, ""); + let gamepadId = gamepad.id.replace(/ \(.*?Vendor: \w+ Product: \w+\)$/, ""); text += ` - ${gamepadId}`; let status; if (gamepad.connected) status = (gamepad.vibrationActuator ? "✅" : "❌") + " " + t("vibration-status"); @@ -6800,8 +6722,7 @@ function showGamepadToast(gamepad) { Toast.show(text, status, { instant: !1 }); } function addCss() { - let css = `:root{--bx-title-font:Bahnschrift,Arial,Helvetica,sans-serif;--bx-title-font-semibold:Bahnschrift Semibold,Arial,Helvetica,sans-serif;--bx-normal-font:"Segoe UI",Arial,Helvetica,sans-serif;--bx-monospaced-font:Consolas,"Courier New",Courier,monospace;--bx-promptfont-font:promptfont;--bx-button-height:40px;--bx-default-button-color:#2d3036;--bx-default-button-rgb:45,48,54;--bx-default-button-hover-color:#515863;--bx-default-button-hover-rgb:81,88,99;--bx-default-button-active-color:#222428;--bx-default-button-active-rgb:34,36,40;--bx-default-button-disabled-color:#8e8e8e;--bx-default-button-disabled-rgb:142,142,142;--bx-primary-button-color:#008746;--bx-primary-button-rgb:0,135,70;--bx-primary-button-hover-color:#04b358;--bx-primary-button-hover-rgb:4,179,88;--bx-primary-button-active-color:#044e2a;--bx-primary-button-active-rgb:4,78,42;--bx-primary-button-disabled-color:#448262;--bx-primary-button-disabled-rgb:68,130,98;--bx-danger-button-color:#c10404;--bx-danger-button-rgb:193,4,4;--bx-danger-button-hover-color:#e61d1d;--bx-danger-button-hover-rgb:230,29,29;--bx-danger-button-active-color:#a26c6c;--bx-danger-button-active-rgb:162,108,108;--bx-danger-button-disabled-color:#df5656;--bx-danger-button-disabled-rgb:223,86,86;--bx-fullscreen-text-z-index:99999;--bx-toast-z-index:60000;--bx-dialog-z-index:50000;--bx-dialog-overlay-z-index:40200;--bx-stats-bar-z-index:40100;--bx-mkb-pointer-lock-msg-z-index:40000;--bx-navigation-dialog-z-index:30100;--bx-navigation-dialog-overlay-z-index:30000;--bx-game-bar-z-index:10000;--bx-screenshot-animation-z-index:9000;--bx-wait-time-box-z-index:1000}@font-face{font-family:'promptfont';src:url("https://redphx.github.io/better-xcloud/fonts/promptfont.otf")}div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module__hiddenContainer]){opacity:0;pointer-events:none !important;position:absolute;top:-9999px;left:-9999px}@media screen and (max-width:640px){header a[href="/play"]{display:none}}.bx-full-width{width:100% !important}.bx-full-height{height:100% !important}.bx-no-scroll{overflow:hidden !important}.bx-hide-scroll-bar{scrollbar-width:none}.bx-hide-scroll-bar::-webkit-scrollbar{display:none}.bx-gone{display:none !important}.bx-offscreen{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-hidden{visibility:hidden !important}.bx-invisible{opacity:0}.bx-unclickable{pointer-events:none}.bx-pixel{width:1px !important;height:1px !important}.bx-no-margin{margin:0 !important}.bx-no-padding{padding:0 !important}.bx-prompt{font-family:var(--bx-promptfont-font)}.bx-line-through{text-decoration:line-through !important}.bx-normal-case{text-transform:none !important}.bx-normal-link{text-transform:none !important;text-align:left !important;font-weight:400 !important;font-family:var(--bx-normal-font) !important}select[multiple]{overflow:auto}#headerArea,#uhfSkipToMain,.uhf-footer{display:none}div[class*=NotFocusedDialog]{position:absolute !important;top:-9999px !important;left:-9999px !important;width:0 !important;height:0 !important}#game-stream video:not([src]){visibility:hidden}div[class*=SupportedInputsBadge]:not(:has(:nth-child(2))),div[class*=SupportedInputsBadge] svg:first-of-type{display:none}.bx-game-tile-wait-time{position:absolute;top:0;left:0;z-index:1;background:rgba(0,0,0,0.549);display:flex;border-radius:4px 0 4px 0;align-items:center;padding:4px 8px}.bx-game-tile-wait-time svg{width:14px;height:16px;margin-right:2px}.bx-game-tile-wait-time span{display:inline-block;height:16px;line-height:16px;font-size:12px;font-weight:bold;margin-left:2px}.bx-fullscreen-text{position:fixed;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,0.8);z-index:var(--bx-fullscreen-text-z-index);line-height:100vh;color:#fff;text-align:center;font-weight:400;font-family:var(--bx-normal-font);font-size:1.3rem;user-select:none;-webkit-user-select:none}#root section[class*=DeviceCodePage-module__page]{margin-left:20px !important;margin-right:20px !important;margin-top:20px !important;max-width:800px !important}#root div[class*=DeviceCodePage-module__back]{display:none}.bx-button{--button-rgb:var(--bx-default-button-rgb);--button-hover-rgb:var(--bx-default-button-hover-rgb);--button-active-rgb:var(--bx-default-button-active-rgb);--button-disabled-rgb:var(--bx-default-button-disabled-rgb);background-color:rgb(var(--button-rgb));user-select:none;-webkit-user-select:none;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;border:none;font-weight:400;height:var(--bx-button-height);border-radius:4px;padding:0 8px;text-transform:uppercase;cursor:pointer;overflow:hidden}.bx-button:not([disabled]):active{background-color:rgb(var(--button-active-rgb))}.bx-button:focus{outline:none !important}.bx-button:not([disabled]):not(:active):hover,.bx-button:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button:disabled{cursor:default;background-color:rgb(var(--button-disabled-rgb))}.bx-button.bx-ghost{background-color:transparent}.bx-button.bx-ghost:not([disabled]):not(:active):hover,.bx-button.bx-ghost:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button.bx-primary{--button-rgb:var(--bx-primary-button-rgb)}.bx-button.bx-primary:not([disabled]):active{--button-active-rgb:var(--bx-primary-button-active-rgb)}.bx-button.bx-primary:not([disabled]):not(:active):hover,.bx-button.bx-primary:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-primary-button-hover-rgb)}.bx-button.bx-primary:disabled{--button-disabled-rgb:var(--bx-primary-button-disabled-rgb)}.bx-button.bx-danger{--button-rgb:var(--bx-danger-button-rgb)}.bx-button.bx-danger:not([disabled]):active{--button-active-rgb:var(--bx-danger-button-active-rgb)}.bx-button.bx-danger:not([disabled]):not(:active):hover,.bx-button.bx-danger:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-danger-button-hover-rgb)}.bx-button.bx-danger:disabled{--button-disabled-rgb:var(--bx-danger-button-disabled-rgb)}.bx-button.bx-frosted{--button-alpha:.2;background-color:rgba(var(--button-rgb), var(--button-alpha));backdrop-filter:blur(4px) brightness(1.5)}.bx-button.bx-frosted:not([disabled]):not(:active):hover,.bx-button.bx-frosted:not([disabled]):not(:active).bx-focusable:focus{background-color:rgba(var(--button-hover-rgb), var(--button-alpha))}.bx-button.bx-drop-shadow{box-shadow:0 0 4px rgba(0,0,0,0.502)}.bx-button.bx-tall{height:calc(var(--bx-button-height) * 1.5) !important}.bx-button.bx-circular{border-radius:var(--bx-button-height);height:var(--bx-button-height)}.bx-button svg{display:inline-block;width:16px;height:var(--bx-button-height)}.bx-button span{display:inline-block;line-height:var(--bx-button-height);vertical-align:middle;color:#fff;overflow:hidden;white-space:nowrap}.bx-button span:not(:only-child){margin-left:10px}.bx-focusable{position:relative;overflow:visible}.bx-focusable::after{border:2px solid transparent;border-radius:10px}.bx-focusable:focus::after{content:'';border-color:#fff;position:absolute;top:-6px;left:-6px;right:-6px;bottom:-6px}html[data-active-input=touch] .bx-focusable:focus::after,html[data-active-input=mouse] .bx-focusable:focus::after{border-color:transparent !important}.bx-focusable.bx-circular::after{border-radius:var(--bx-button-height)}a.bx-button{display:inline-block}a.bx-button.bx-full-width{text-align:center}button.bx-inactive{pointer-events:none;opacity:.2;background:transparent !important}.bx-header-remote-play-button{height:auto;margin-right:8px !important}.bx-header-remote-play-button svg{width:24px;height:24px}.bx-header-settings-button{line-height:30px;font-size:14px;text-transform:uppercase;position:relative}.bx-header-settings-button[data-update-available]::before{content:'🌟' !important;line-height:var(--bx-button-height);display:inline-block;margin-left:4px}.bx-dialog-overlay{position:fixed;inset:0;z-index:var(--bx-dialog-overlay-z-index);background:#000;opacity:50%}.bx-dialog{display:flex;flex-flow:column;max-height:90vh;position:fixed;top:50%;left:50%;margin-right:-50%;transform:translate(-50%,-50%);min-width:420px;padding:20px;border-radius:8px;z-index:var(--bx-dialog-z-index);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-normal-font);box-shadow:0 0 6px #000;user-select:none;-webkit-user-select:none}.bx-dialog *:focus{outline:none !important}.bx-dialog h2{display:flex;margin-bottom:12px}.bx-dialog h2 b{flex:1;color:#fff;display:block;font-family:var(--bx-title-font);font-size:26px;font-weight:400;line-height:var(--bx-button-height)}.bx-dialog.bx-binding-dialog h2 b{font-family:var(--bx-promptfont-font) !important}.bx-dialog > div{overflow:auto;padding:2px 0}.bx-dialog > button{padding:8px 32px;margin:10px auto 0;border:none;border-radius:4px;display:block;background-color:#2d3036;text-align:center;color:#fff;text-transform:uppercase;font-family:var(--bx-title-font);font-weight:400;line-height:18px;font-size:14px}@media (hover:hover){.bx-dialog > button:hover{background-color:#515863}}.bx-dialog > button:focus{background-color:#515863}@media screen and (max-width:450px){.bx-dialog{min-width:100%}}.bx-navigation-dialog{position:absolute;z-index:var(--bx-navigation-dialog-z-index);font-family:var(--bx-title-font)}.bx-navigation-dialog *:focus{outline:none !important}.bx-navigation-dialog-overlay{position:fixed;background:rgba(11,11,11,0.89);top:0;left:0;right:0;bottom:0;z-index:var(--bx-navigation-dialog-overlay-z-index)}.bx-navigation-dialog-overlay[data-is-playing="true"]{background:transparent}.bx-settings-dialog{display:flex;position:fixed;top:0;right:0;bottom:0;opacity:.98;user-select:none;-webkit-user-select:none}.bx-settings-dialog .bx-focusable::after{border-radius:4px}.bx-settings-dialog .bx-focusable:focus::after{top:0;left:0;right:0;bottom:0}.bx-settings-dialog .bx-settings-reload-note{font-size:.8rem;display:block;padding:8px;font-style:italic;font-weight:normal;height:var(--bx-button-height)}.bx-settings-dialog input{accent-color:var(--bx-primary-button-color)}.bx-settings-dialog input:focus{accent-color:var(--bx-danger-button-color)}.bx-settings-dialog select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;border:none;color:#fff}.bx-settings-dialog select option:disabled{display:none}.bx-settings-dialog input[type=checkbox]:focus,.bx-settings-dialog select:focus{filter:drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff)}.bx-settings-dialog a{color:#1c9d1c;text-decoration:none}.bx-settings-dialog a:hover,.bx-settings-dialog a:focus{color:#5dc21e}.bx-settings-tabs-container{position:fixed;width:48px;max-height:100vh;display:flex;flex-direction:column}.bx-settings-tabs-container > div:last-of-type{display:flex;flex-direction:column;align-items:end}.bx-settings-tabs-container > div:last-of-type button{flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0;margin-top:8px;height:unset;padding:8px 10px}.bx-settings-tabs-container > div:last-of-type button svg{width:16px;height:16px}.bx-settings-tabs{display:flex;flex-direction:column;border-radius:0 0 0 8px;box-shadow:0 0 6px #000;overflow:overlay;flex:1}.bx-settings-tabs svg{width:24px;height:24px;padding:10px;flex-shrink:0;box-sizing:content-box;background:#131313;cursor:pointer;border-left:4px solid #1e1e1e}.bx-settings-tabs svg.bx-active{background:#222;border-color:#008746}.bx-settings-tabs svg:not(.bx-active):hover{background:#2f2f2f;border-color:#484848}.bx-settings-tabs svg:focus{border-color:#fff}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]{background:var(--bx-danger-button-color) !important}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]:hover{background:var(--bx-danger-button-hover-color) !important}.bx-settings-tab-contents{flex-direction:column;padding:10px;margin-left:48px;width:450px;max-width:calc(100vw - tabsWidth);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-title-font);text-align:center;box-shadow:0 0 6px #000;overflow:overlay;z-index:1}.bx-settings-tab-contents > div[data-tab-group=mkb]{display:flex;flex-direction:column;height:100%;overflow:hidden}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=true] > div:first-of-type{display:none}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=true] > div:last-of-type{display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=false] > div:first-of-type{display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=false] > div:last-of-type{display:none}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-profile{width:100%;height:36px;display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-note{margin-top:10px;font-size:14px}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row{display:flex;margin-bottom:10px}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row label.bx-prompt{flex:1;font-size:26px;margin-bottom:0}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions{flex:2;position:relative}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions select{position:absolute;width:100%;height:100%;display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions select:last-of-type{opacity:0;z-index:calc(var(--bx-settings-z-index) + 1)}.bx-settings-tab-contents .bx-top-buttons{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}.bx-settings-tab-contents .bx-top-buttons .bx-button{display:block}.bx-settings-tab-contents h2{margin:16px 0 8px 0;display:flex;align-items:center}.bx-settings-tab-contents h2:first-of-type{margin-top:0}.bx-settings-tab-contents h2 span{display:inline-block;font-size:20px;font-weight:bold;text-align:left;flex:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}@media (max-width:500px){.bx-settings-tab-contents{width:calc(100vw - 48px)}}.bx-settings-row{display:flex;gap:10px;padding:16px 10px;margin:0;background:#2a2a2a;border-bottom:1px solid #343434}.bx-settings-row:hover,.bx-settings-row:focus-within{background-color:#242424}.bx-settings-row:not(:has(> input[type=checkbox])){flex-wrap:wrap}.bx-settings-row > span.bx-settings-label{font-size:14px;display:block;text-align:left;align-self:center;margin-bottom:0 !important;flex:1}.bx-settings-row > span.bx-settings-label + *{margin:0 0 0 auto}.bx-settings-dialog-note{display:block;color:#afafb0;font-size:12px;font-weight:lighter;font-style:italic}.bx-settings-dialog-note:not(:has(a)){margin-top:4px}.bx-settings-dialog-note a{display:inline-block;padding:4px}.bx-settings-custom-user-agent{display:block;width:100%;padding:6px}.bx-donation-link{display:block;text-align:center;text-decoration:none;height:20px;line-height:20px;font-size:14px;margin-top:10px}.bx-debug-info button{margin-top:10px}.bx-debug-info pre{margin-top:10px;cursor:copy;color:#fff;padding:8px;border:1px solid #2d2d2d;background:#212121;white-space:break-spaces;text-align:left}.bx-debug-info pre:hover{background:#272727}.bx-settings-app-version{margin-top:10px;text-align:center;color:#747474;font-size:12px}.bx-note-unsupported{display:block;font-size:12px;font-style:italic;font-weight:normal;color:#828282}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:has(+ .bx-settings-row){border-top-left-radius:10px;border-top-right-radius:10px}.bx-settings-tab-contents > div .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-bottom-left-radius:10px;border-bottom-right-radius:10px}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-radius:10px}.bx-suggest-toggler{text-align:left;display:flex;border-radius:4px;overflow:hidden;background:#003861}.bx-suggest-toggler label{flex:1;margin-bottom:0;padding:10px;background:#004f87}.bx-suggest-toggler span{display:inline-block;align-self:center;padding:10px;width:40px;text-align:center}.bx-suggest-toggler:hover,.bx-suggest-toggler:focus{cursor:pointer;background:#005da1}.bx-suggest-toggler:hover label,.bx-suggest-toggler:focus label{background:#006fbe}.bx-suggest-toggler[bx-open] span{transform:rotate(90deg)}.bx-suggest-toggler[bx-open]+ .bx-suggest-box{display:block}.bx-suggest-box{display:none;background:#161616;padding:10px;box-shadow:0 0 12px #0f0f0f inset;border-radius:10px}.bx-suggest-wrapper{display:flex;flex-direction:column;gap:10px;margin:10px}.bx-suggest-note{font-size:11px;color:#8c8c8c;font-style:italic;font-weight:100}.bx-suggest-link{font-size:14px;display:inline-block;margin-top:4px;padding:4px}.bx-suggest-row{display:flex;flex-direction:row;gap:10px}.bx-suggest-row label{flex:1;overflow:overlay;border-radius:4px}.bx-suggest-row label .bx-suggest-label{background:#323232;padding:4px 10px;font-size:12px;text-align:left}.bx-suggest-row label .bx-suggest-value{padding:6px;font-size:14px}.bx-suggest-row label .bx-suggest-value.bx-suggest-change{background-color:var(--bx-warning-color)}.bx-suggest-row.bx-suggest-ok input{visibility:hidden}.bx-suggest-row.bx-suggest-ok .bx-suggest-label{background-color:#008114}.bx-suggest-row.bx-suggest-ok .bx-suggest-value{background-color:#13a72a}.bx-suggest-row.bx-suggest-change .bx-suggest-label{background-color:#a65e08}.bx-suggest-row.bx-suggest-change .bx-suggest-value{background-color:#d57f18}.bx-suggest-row.bx-suggest-change:hover label{cursor:pointer}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-label{background-color:#995707}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-value{background-color:#bd7115}.bx-suggest-row.bx-suggest-change input:not(:checked) + label{opacity:.5}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-label{background-color:#2a2a2a}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-value{background-color:#393939}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label{opacity:1}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-label{background-color:#202020}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-value{background-color:#303030}.bx-toast{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:24px;transform:translate(-50%,0);background:#000;border-radius:16px;color:#fff;z-index:var(--bx-toast-z-index);font-family:var(--bx-normal-font);border:2px solid #fff;display:flex;align-items:center;opacity:0;overflow:clip;transition:opacity .2s ease-in}.bx-toast.bx-show{opacity:.85}.bx-toast.bx-hide{opacity:0;pointer-events:none}.bx-toast-msg{font-size:14px;display:inline-block;padding:12px 16px;white-space:pre}.bx-toast-status{font-weight:bold;font-size:14px;text-transform:uppercase;display:inline-block;background:#515863;padding:12px 16px;color:#fff;white-space:pre}.bx-wait-time-box{position:fixed;top:0;right:0;background-color:rgba(0,0,0,0.8);color:#fff;z-index:var(--bx-wait-time-box-z-index);padding:12px;border-radius:0 0 0 8px}.bx-wait-time-box label{display:block;text-transform:uppercase;text-align:right;font-size:12px;font-weight:bold;margin:0}.bx-wait-time-box span{display:block;font-family:var(--bx-monospaced-font);text-align:right;font-size:16px;margin-bottom:10px}.bx-wait-time-box span:last-of-type{margin-bottom:0}.bx-remote-play-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;width:420px;max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:20px}.bx-remote-play-container > .bx-button{display:table;margin:0 0 0 auto}.bx-remote-play-settings{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #2d2d2d}.bx-remote-play-settings > div{display:flex}.bx-remote-play-settings label{flex:1}.bx-remote-play-settings label p{margin:4px 0 0;padding:0;color:#888;font-size:12px}.bx-remote-play-resolution{display:block}.bx-remote-play-resolution input[type="radio"]{accent-color:var(--bx-primary-button-color);margin-right:6px}.bx-remote-play-resolution input[type="radio"]:focus{accent-color:var(--bx-primary-button-hover-color)}.bx-remote-play-device-wrapper{display:flex;margin-bottom:12px}.bx-remote-play-device-wrapper:last-child{margin-bottom:2px}.bx-remote-play-device-info{flex:1;padding:4px 0}.bx-remote-play-device-name{font-size:20px;font-weight:bold;display:inline-block;vertical-align:middle}.bx-remote-play-console-type{font-size:12px;background:#004c87;color:#fff;display:inline-block;border-radius:14px;padding:2px 10px;margin-left:8px;vertical-align:middle}.bx-remote-play-power-state{color:#888;font-size:12px}.bx-remote-play-connect-button{min-height:100%;margin:4px 0}.bx-remote-play-buttons{display:flex;justify-content:space-between}.bx-select{display:flex;align-items:center;flex:0 1 auto}.bx-select select{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-select > div,.bx-select button.bx-select-value{min-width:120px;text-align:left;margin:0 8px;line-height:24px;vertical-align:middle;background:#fff;color:#000;border-radius:4px;padding:2px 8px;flex:1}.bx-select > div{display:inline-block}.bx-select > div input{display:inline-block;margin-right:8px}.bx-select > div label{margin-bottom:0;font-size:14px;width:100%}.bx-select > div label span{display:block;font-size:10px;font-weight:bold;text-align:left;line-height:initial}.bx-select button.bx-select-value{border:none;display:inline-flex;cursor:pointer;min-height:30px;font-size:.9rem;align-items:center}.bx-select button.bx-select-value span{flex:1;text-align:left;display:inline-block}.bx-select button.bx-select-value input{margin:0 4px;accent-color:var(--bx-primary-button-color);pointer-events:none}.bx-select button.bx-select-value:hover input,.bx-select button.bx-select-value:focus input{accent-color:var(--bx-danger-button-color)}.bx-select button.bx-select-value:hover::after,.bx-select button.bx-select-value:focus::after{border-color:#4d4d4d !important}.bx-select button.bx-button{border:none;height:24px;width:24px;padding:0;line-height:24px;color:#fff;border-radius:4px;font-weight:bold;font-size:12px;font-family:var(--bx-monospaced-font);flex-shrink:0}.bx-select button.bx-button span{line-height:unset}.bx-guide-home-achievements-progress{display:flex;gap:10px;flex-direction:row}.bx-guide-home-achievements-progress .bx-button{margin-bottom:0 !important}html[data-xds-platform=tv] .bx-guide-home-achievements-progress{flex-direction:column}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress{flex-direction:row}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:first-of-type{flex:1}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:last-of-type{width:40px}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:last-of-type span{display:none}.bx-guide-home-buttons > div{display:flex;flex-direction:row;gap:12px}html[data-xds-platform=tv] .bx-guide-home-buttons > div{flex-direction:column}html[data-xds-platform=tv] .bx-guide-home-buttons > div button{margin-bottom:0 !important}html:not([data-xds-platform=tv]) .bx-guide-home-buttons > div button span{display:none}.bx-guide-home-buttons[data-is-playing="true"] button[data-state='normal']{display:none}.bx-guide-home-buttons[data-is-playing="false"] button[data-state='playing']{display:none}div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]{overflow:visible}.bx-stream-menu-button-on{fill:#000 !important;background-color:#2d2d2d !important;color:#000 !important}.bx-stream-refresh-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important}body[data-media-type=default] .bx-stream-refresh-button{left:calc(env(safe-area-inset-left, 0px) + 11px) !important}body[data-media-type=tv] .bx-stream-refresh-button{top:calc(var(--gds-focus-borderSize) + 80px) !important}.bx-stream-home-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px * 2) !important}body[data-media-type=default] .bx-stream-home-button{left:calc(env(safe-area-inset-left, 0px) + 12px) !important}body[data-media-type=tv] .bx-stream-home-button{top:calc(var(--gds-focus-borderSize) + 80px * 2) !important}div[data-testid=media-container]{display:flex}div[data-testid=media-container].bx-taking-screenshot:before{animation:bx-anim-taking-screenshot .5s ease;content:' ';position:absolute;width:100%;height:100%;z-index:var(--bx-screenshot-animation-z-index)}#game-stream video{margin:auto;align-self:center;background:#000}#game-stream canvas{position:absolute;align-self:center;margin:auto;left:0;right:0}#gamepass-dialog-root div[class^=Guide-module__guide] .bx-button{overflow:visible;margin-bottom:12px}@-moz-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-webkit-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-o-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}.bx-number-stepper{text-align:center}.bx-number-stepper span{display:inline-block;min-width:40px;font-family:var(--bx-monospaced-font);font-size:13px;margin:0 4px}.bx-number-stepper button{border:none;width:24px;height:24px;margin:0;line-height:24px;background-color:var(--bx-default-button-color);color:#fff;border-radius:4px;font-weight:bold;font-size:14px;font-family:var(--bx-monospaced-font)}@media (hover:hover){.bx-number-stepper button:hover{background-color:var(--bx-default-button-hover-color)}}.bx-number-stepper button:active{background-color:var(--bx-default-button-hover-color)}.bx-number-stepper button:disabled + span{font-family:var(--bx-title-font)}.bx-number-stepper input[type="range"]{display:block;margin:12px auto 2px;width:180px;color:#959595 !important}.bx-number-stepper input[type=range]:disabled,.bx-number-stepper button:disabled{display:none}.bx-number-stepper[data-disabled=true] input[type=range],.bx-number-stepper[data-disabled=true] button{display:none}#bx-game-bar{z-index:var(--bx-game-bar-z-index);position:fixed;bottom:0;width:40px;height:90px;overflow:visible;cursor:pointer}#bx-game-bar > svg{display:none;pointer-events:none;position:absolute;height:28px;margin-top:16px}@media (hover:hover){#bx-game-bar:hover > svg{display:block}}#bx-game-bar .bx-game-bar-container{opacity:0;position:absolute;display:flex;overflow:hidden;background:rgba(26,27,30,0.91);box-shadow:0 0 6px #1c1c1c;transition:opacity .1s ease-in}#bx-game-bar .bx-game-bar-container.bx-show{opacity:.9}#bx-game-bar .bx-game-bar-container.bx-show + svg{display:none !important}#bx-game-bar .bx-game-bar-container.bx-hide{opacity:0;pointer-events:none}#bx-game-bar .bx-game-bar-container button{width:60px;height:60px;border-radius:0}#bx-game-bar .bx-game-bar-container button svg{width:28px;height:28px;transition:transform .08s ease 0s}#bx-game-bar .bx-game-bar-container button:hover{border-radius:0}#bx-game-bar .bx-game-bar-container button:active svg{transform:scale(.75)}#bx-game-bar .bx-game-bar-container button.bx-activated{background-color:#fff}#bx-game-bar .bx-game-bar-container button.bx-activated svg{filter:invert(1)}#bx-game-bar .bx-game-bar-container div[data-activated] button{display:none}#bx-game-bar .bx-game-bar-container div[data-activated='false'] button:first-of-type{display:block}#bx-game-bar .bx-game-bar-container div[data-activated='true'] button:last-of-type{display:block}#bx-game-bar[data-position="bottom-left"]{left:0;direction:ltr}#bx-game-bar[data-position="bottom-left"] .bx-game-bar-container{border-radius:0 10px 10px 0}#bx-game-bar[data-position="bottom-right"]{right:0;direction:rtl}#bx-game-bar[data-position="bottom-right"] .bx-game-bar-container{direction:ltr;border-radius:10px 0 0 10px}.bx-badges{margin-left:0;user-select:none;-webkit-user-select:none}.bx-badge{border:none;display:inline-block;line-height:24px;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;font-weight:400;margin:0 8px 8px 0;box-shadow:0 0 6px #000;border-radius:4px}.bx-badge-name{background-color:#2d3036;border-radius:4px 0 0 4px}.bx-badge-name svg{width:16px;height:16px}.bx-badge-value{background-color:#808080;border-radius:0 4px 4px 0}.bx-badge-name,.bx-badge-value{display:inline-block;padding:0 8px;line-height:30px;vertical-align:bottom}.bx-badge-battery[data-charging=true] span:first-of-type::after{content:' ⚡️'}div[class^=StreamMenu-module__container] .bx-badges{position:absolute;max-width:500px}#gamepass-dialog-root .bx-badges{position:fixed;top:60px;left:460px;max-width:500px}@media (min-width:568px) and (max-height:480px){#gamepass-dialog-root .bx-badges{position:unset;top:unset;left:unset;margin:8px 0}}.bx-stats-bar{display:flex;flex-direction:row;gap:8px;user-select:none;-webkit-user-select:none;position:fixed;top:0;background-color:#000;color:#fff;font-family:var(--bx-monospaced-font);font-size:.9rem;padding-left:8px;z-index:var(--bx-stats-bar-z-index);text-wrap:nowrap}.bx-stats-bar[data-stats*="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats*="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats*="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats*="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats*="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats*="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats*="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats*="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats*="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats*="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats*="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats*="[ul]"] > .bx-stat-ul{display:inline-flex;align-items:baseline}.bx-stats-bar[data-stats$="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats$="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats$="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats$="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats$="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats$="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats$="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats$="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats$="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats$="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats$="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats$="[ul]"] > .bx-stat-ul{border-right:none}.bx-stats-bar::before{display:none;content:'👀';vertical-align:middle;margin-right:8px}.bx-stats-bar[data-display=glancing]::before{display:inline-block}.bx-stats-bar[data-position=top-left]{left:0;border-radius:0 0 4px 0}.bx-stats-bar[data-position=top-right]{right:0;border-radius:0 0 0 4px}.bx-stats-bar[data-position=top-center]{transform:translate(-50%,0);left:50%;border-radius:0 0 4px 4px}.bx-stats-bar[data-transparent=true]{background:none;filter:drop-shadow(1px 0 0 rgba(0,0,0,0.941)) drop-shadow(-1px 0 0 rgba(0,0,0,0.941)) drop-shadow(0 1px 0 rgba(0,0,0,0.941)) drop-shadow(0 -1px 0 rgba(0,0,0,0.941))}.bx-stats-bar > div{display:none;border-right:1px solid #fff;padding-right:8px}.bx-stats-bar label{margin:0 8px 0 0;font-family:var(--bx-title-font);font-size:70%;font-weight:bold;vertical-align:middle;cursor:help}.bx-stats-bar span{min-width:60px;display:inline-block;text-align:right;vertical-align:middle}.bx-stats-bar span[data-grade=good]{color:#6bffff}.bx-stats-bar span[data-grade=ok]{color:#fff16b}.bx-stats-bar span[data-grade=bad]{color:#ff5f5f}.bx-stats-bar span:first-of-type{min-width:22px}.bx-mkb-settings{display:flex;flex-direction:column;flex:1;padding-bottom:10px;overflow:hidden}.bx-mkb-settings select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;text-align:right;border:none;color:#fff}.bx-mkb-pointer-lock-msg{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:50%;transform:translateX(-50%) translateY(-50%);margin:auto;background:#151515;z-index:var(--bx-mkb-pointer-lock-msg-z-index);color:#fff;text-align:center;font-weight:400;font-family:"Segoe UI",Arial,Helvetica,sans-serif;font-size:1.3rem;padding:12px;border-radius:8px;align-items:center;box-shadow:0 0 6px #000;min-width:220px;opacity:.9}.bx-mkb-pointer-lock-msg:hover{opacity:1}.bx-mkb-pointer-lock-msg > div:first-of-type{display:flex;flex-direction:column;text-align:left}.bx-mkb-pointer-lock-msg p{margin:0}.bx-mkb-pointer-lock-msg p:first-child{font-size:22px;margin-bottom:4px;font-weight:bold}.bx-mkb-pointer-lock-msg p:last-child{font-size:12px;font-style:italic}.bx-mkb-pointer-lock-msg > div:last-of-type{margin-top:10px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='native'] button:first-of-type{margin-bottom:8px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div{display:flex;flex-flow:row;margin-top:8px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button{flex:1}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button:first-of-type{margin-right:5px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button:last-of-type{margin-left:5px}.bx-mkb-preset-tools{display:flex;margin-bottom:12px}.bx-mkb-preset-tools select{flex:1}.bx-mkb-preset-tools button{margin-left:6px}.bx-mkb-settings-rows{flex:1;overflow:scroll}.bx-mkb-key-row{display:flex;margin-bottom:10px;align-items:center}.bx-mkb-key-row label{margin-bottom:0;font-family:var(--bx-promptfont-font);font-size:26px;text-align:center;width:26px;height:32px;line-height:32px}.bx-mkb-key-row button{flex:1;height:32px;line-height:32px;margin:0 0 0 10px;background:transparent;border:none;color:#fff;border-radius:0;border-left:1px solid #373737}.bx-mkb-key-row button:hover{background:transparent;cursor:default}.bx-mkb-settings.bx-editing .bx-mkb-key-row button{background:#393939;border-radius:4px;border:none}.bx-mkb-settings.bx-editing .bx-mkb-key-row button:hover{background:#333;cursor:pointer}.bx-mkb-action-buttons > div{text-align:right;display:none}.bx-mkb-action-buttons button{margin-left:8px}.bx-mkb-settings:not(.bx-editing) .bx-mkb-action-buttons > div:first-child{display:block}.bx-mkb-settings.bx-editing .bx-mkb-action-buttons > div:last-child{display:block}.bx-mkb-note{display:block;margin:16px 0 10px;font-size:12px}.bx-mkb-note:first-of-type{margin-top:0}.bx-product-details-buttons{display:flex;gap:10px;flex-direction:row}.bx-product-details-buttons button{max-width:max-content;margin:10px 0 0 0;display:flex}@media (min-width:568px) and (max-height:480px){.bx-product-details-buttons{flex-direction:column}.bx-product-details-buttons button{margin:8px 0 0 10px}}`; - const PREF_HIDE_SECTIONS = getPref("ui_hide_sections"), selectorToHide = []; + let css = `:root{--bx-title-font:Bahnschrift,Arial,Helvetica,sans-serif;--bx-title-font-semibold:Bahnschrift Semibold,Arial,Helvetica,sans-serif;--bx-normal-font:"Segoe UI",Arial,Helvetica,sans-serif;--bx-monospaced-font:Consolas,"Courier New",Courier,monospace;--bx-promptfont-font:promptfont;--bx-button-height:40px;--bx-default-button-color:#2d3036;--bx-default-button-rgb:45,48,54;--bx-default-button-hover-color:#515863;--bx-default-button-hover-rgb:81,88,99;--bx-default-button-active-color:#222428;--bx-default-button-active-rgb:34,36,40;--bx-default-button-disabled-color:#8e8e8e;--bx-default-button-disabled-rgb:142,142,142;--bx-primary-button-color:#008746;--bx-primary-button-rgb:0,135,70;--bx-primary-button-hover-color:#04b358;--bx-primary-button-hover-rgb:4,179,88;--bx-primary-button-active-color:#044e2a;--bx-primary-button-active-rgb:4,78,42;--bx-primary-button-disabled-color:#448262;--bx-primary-button-disabled-rgb:68,130,98;--bx-danger-button-color:#c10404;--bx-danger-button-rgb:193,4,4;--bx-danger-button-hover-color:#e61d1d;--bx-danger-button-hover-rgb:230,29,29;--bx-danger-button-active-color:#a26c6c;--bx-danger-button-active-rgb:162,108,108;--bx-danger-button-disabled-color:#df5656;--bx-danger-button-disabled-rgb:223,86,86;--bx-fullscreen-text-z-index:99999;--bx-toast-z-index:60000;--bx-dialog-z-index:50000;--bx-dialog-overlay-z-index:40200;--bx-stats-bar-z-index:40100;--bx-mkb-pointer-lock-msg-z-index:40000;--bx-navigation-dialog-z-index:30100;--bx-navigation-dialog-overlay-z-index:30000;--bx-game-bar-z-index:10000;--bx-screenshot-animation-z-index:9000;--bx-wait-time-box-z-index:1000}@font-face{font-family:'promptfont';src:url("https://redphx.github.io/better-xcloud/fonts/promptfont.otf")}div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module__hiddenContainer]){opacity:0;pointer-events:none !important;position:absolute;top:-9999px;left:-9999px}@media screen and (max-width:640px){header a[href="/play"]{display:none}}.bx-full-width{width:100% !important}.bx-full-height{height:100% !important}.bx-no-scroll{overflow:hidden !important}.bx-hide-scroll-bar{scrollbar-width:none}.bx-hide-scroll-bar::-webkit-scrollbar{display:none}.bx-gone{display:none !important}.bx-offscreen{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-hidden{visibility:hidden !important}.bx-invisible{opacity:0}.bx-unclickable{pointer-events:none}.bx-pixel{width:1px !important;height:1px !important}.bx-no-margin{margin:0 !important}.bx-no-padding{padding:0 !important}.bx-prompt{font-family:var(--bx-promptfont-font)}.bx-line-through{text-decoration:line-through !important}.bx-normal-case{text-transform:none !important}.bx-normal-link{text-transform:none !important;text-align:left !important;font-weight:400 !important;font-family:var(--bx-normal-font) !important}select[multiple]{overflow:auto}#headerArea,#uhfSkipToMain,.uhf-footer{display:none}div[class*=NotFocusedDialog]{position:absolute !important;top:-9999px !important;left:-9999px !important;width:0 !important;height:0 !important}#game-stream video:not([src]){visibility:hidden}div[class*=SupportedInputsBadge]:not(:has(:nth-child(2))),div[class*=SupportedInputsBadge] svg:first-of-type{display:none}.bx-game-tile-wait-time{position:absolute;top:0;left:0;z-index:1;background:rgba(0,0,0,0.549);display:flex;border-radius:4px 0 4px 0;align-items:center;padding:4px 8px}.bx-game-tile-wait-time svg{width:14px;height:16px;margin-right:2px}.bx-game-tile-wait-time span{display:inline-block;height:16px;line-height:16px;font-size:12px;font-weight:bold;margin-left:2px}.bx-fullscreen-text{position:fixed;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,0.8);z-index:var(--bx-fullscreen-text-z-index);line-height:100vh;color:#fff;text-align:center;font-weight:400;font-family:var(--bx-normal-font);font-size:1.3rem;user-select:none;-webkit-user-select:none}#root section[class*=DeviceCodePage-module__page]{margin-left:20px !important;margin-right:20px !important;margin-top:20px !important;max-width:800px !important}#root div[class*=DeviceCodePage-module__back]{display:none}.bx-button{--button-rgb:var(--bx-default-button-rgb);--button-hover-rgb:var(--bx-default-button-hover-rgb);--button-active-rgb:var(--bx-default-button-active-rgb);--button-disabled-rgb:var(--bx-default-button-disabled-rgb);background-color:rgb(var(--button-rgb));user-select:none;-webkit-user-select:none;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;border:none;font-weight:400;height:var(--bx-button-height);border-radius:4px;padding:0 8px;text-transform:uppercase;cursor:pointer;overflow:hidden}.bx-button:not([disabled]):active{background-color:rgb(var(--button-active-rgb))}.bx-button:focus{outline:none !important}.bx-button:not([disabled]):not(:active):hover,.bx-button:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button:disabled{cursor:default;background-color:rgb(var(--button-disabled-rgb))}.bx-button.bx-ghost{background-color:transparent}.bx-button.bx-ghost:not([disabled]):not(:active):hover,.bx-button.bx-ghost:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button.bx-primary{--button-rgb:var(--bx-primary-button-rgb)}.bx-button.bx-primary:not([disabled]):active{--button-active-rgb:var(--bx-primary-button-active-rgb)}.bx-button.bx-primary:not([disabled]):not(:active):hover,.bx-button.bx-primary:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-primary-button-hover-rgb)}.bx-button.bx-primary:disabled{--button-disabled-rgb:var(--bx-primary-button-disabled-rgb)}.bx-button.bx-danger{--button-rgb:var(--bx-danger-button-rgb)}.bx-button.bx-danger:not([disabled]):active{--button-active-rgb:var(--bx-danger-button-active-rgb)}.bx-button.bx-danger:not([disabled]):not(:active):hover,.bx-button.bx-danger:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-danger-button-hover-rgb)}.bx-button.bx-danger:disabled{--button-disabled-rgb:var(--bx-danger-button-disabled-rgb)}.bx-button.bx-frosted{--button-alpha:.2;background-color:rgba(var(--button-rgb), var(--button-alpha));backdrop-filter:blur(4px) brightness(1.5)}.bx-button.bx-frosted:not([disabled]):not(:active):hover,.bx-button.bx-frosted:not([disabled]):not(:active).bx-focusable:focus{background-color:rgba(var(--button-hover-rgb), var(--button-alpha))}.bx-button.bx-drop-shadow{box-shadow:0 0 4px rgba(0,0,0,0.502)}.bx-button.bx-tall{height:calc(var(--bx-button-height) * 1.5) !important}.bx-button.bx-circular{border-radius:var(--bx-button-height);height:var(--bx-button-height)}.bx-button svg{display:inline-block;width:16px;height:var(--bx-button-height)}.bx-button span{display:inline-block;line-height:var(--bx-button-height);vertical-align:middle;color:#fff;overflow:hidden;white-space:nowrap}.bx-button span:not(:only-child){margin-left:10px}.bx-focusable{position:relative;overflow:visible}.bx-focusable::after{border:2px solid transparent;border-radius:10px}.bx-focusable:focus::after{content:'';border-color:#fff;position:absolute;top:-6px;left:-6px;right:-6px;bottom:-6px}html[data-active-input=touch] .bx-focusable:focus::after,html[data-active-input=mouse] .bx-focusable:focus::after{border-color:transparent !important}.bx-focusable.bx-circular::after{border-radius:var(--bx-button-height)}a.bx-button{display:inline-block}a.bx-button.bx-full-width{text-align:center}button.bx-inactive{pointer-events:none;opacity:.2;background:transparent !important}.bx-header-remote-play-button{height:auto;margin-right:8px !important}.bx-header-remote-play-button svg{width:24px;height:24px}.bx-header-settings-button{line-height:30px;font-size:14px;text-transform:uppercase;position:relative}.bx-header-settings-button[data-update-available]::before{content:'🌟' !important;line-height:var(--bx-button-height);display:inline-block;margin-left:4px}.bx-dialog-overlay{position:fixed;inset:0;z-index:var(--bx-dialog-overlay-z-index);background:#000;opacity:50%}.bx-dialog{display:flex;flex-flow:column;max-height:90vh;position:fixed;top:50%;left:50%;margin-right:-50%;transform:translate(-50%,-50%);min-width:420px;padding:20px;border-radius:8px;z-index:var(--bx-dialog-z-index);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-normal-font);box-shadow:0 0 6px #000;user-select:none;-webkit-user-select:none}.bx-dialog *:focus{outline:none !important}.bx-dialog h2{display:flex;margin-bottom:12px}.bx-dialog h2 b{flex:1;color:#fff;display:block;font-family:var(--bx-title-font);font-size:26px;font-weight:400;line-height:var(--bx-button-height)}.bx-dialog.bx-binding-dialog h2 b{font-family:var(--bx-promptfont-font) !important}.bx-dialog > div{overflow:auto;padding:2px 0}.bx-dialog > button{padding:8px 32px;margin:10px auto 0;border:none;border-radius:4px;display:block;background-color:#2d3036;text-align:center;color:#fff;text-transform:uppercase;font-family:var(--bx-title-font);font-weight:400;line-height:18px;font-size:14px}@media (hover:hover){.bx-dialog > button:hover{background-color:#515863}}.bx-dialog > button:focus{background-color:#515863}@media screen and (max-width:450px){.bx-dialog{min-width:100%}}.bx-navigation-dialog{position:absolute;z-index:var(--bx-navigation-dialog-z-index);font-family:var(--bx-title-font)}.bx-navigation-dialog *:focus{outline:none !important}.bx-navigation-dialog-overlay{position:fixed;background:rgba(11,11,11,0.89);top:0;left:0;right:0;bottom:0;z-index:var(--bx-navigation-dialog-overlay-z-index)}.bx-navigation-dialog-overlay[data-is-playing="true"]{background:transparent}.bx-settings-dialog{display:flex;position:fixed;top:0;right:0;bottom:0;opacity:.98;user-select:none;-webkit-user-select:none}.bx-settings-dialog .bx-focusable::after{border-radius:4px}.bx-settings-dialog .bx-focusable:focus::after{top:0;left:0;right:0;bottom:0}.bx-settings-dialog .bx-settings-reload-note{font-size:.8rem;display:block;padding:8px;font-style:italic;font-weight:normal;height:var(--bx-button-height)}.bx-settings-dialog input{accent-color:var(--bx-primary-button-color)}.bx-settings-dialog input:focus{accent-color:var(--bx-danger-button-color)}.bx-settings-dialog select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;border:none;color:#fff}.bx-settings-dialog select option:disabled{display:none}.bx-settings-dialog input[type=checkbox]:focus,.bx-settings-dialog select:focus{filter:drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff)}.bx-settings-dialog a{color:#1c9d1c;text-decoration:none}.bx-settings-dialog a:hover,.bx-settings-dialog a:focus{color:#5dc21e}.bx-settings-tabs-container{position:fixed;width:48px;max-height:100vh;display:flex;flex-direction:column}.bx-settings-tabs-container > div:last-of-type{display:flex;flex-direction:column;align-items:end}.bx-settings-tabs-container > div:last-of-type button{flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0;margin-top:8px;height:unset;padding:8px 10px}.bx-settings-tabs-container > div:last-of-type button svg{width:16px;height:16px}.bx-settings-tabs{display:flex;flex-direction:column;border-radius:0 0 0 8px;box-shadow:0 0 6px #000;overflow:overlay;flex:1}.bx-settings-tabs svg{width:24px;height:24px;padding:10px;flex-shrink:0;box-sizing:content-box;background:#131313;cursor:pointer;border-left:4px solid #1e1e1e}.bx-settings-tabs svg.bx-active{background:#222;border-color:#008746}.bx-settings-tabs svg:not(.bx-active):hover{background:#2f2f2f;border-color:#484848}.bx-settings-tabs svg:focus{border-color:#fff}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]{background:var(--bx-danger-button-color) !important}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]:hover{background:var(--bx-danger-button-hover-color) !important}.bx-settings-tab-contents{flex-direction:column;padding:10px;margin-left:48px;width:450px;max-width:calc(100vw - tabsWidth);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-title-font);text-align:center;box-shadow:0 0 6px #000;overflow:overlay;z-index:1}.bx-settings-tab-contents > div[data-tab-group=mkb]{display:flex;flex-direction:column;height:100%;overflow:hidden}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=true] > div:first-of-type{display:none}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=true] > div:last-of-type{display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=false] > div:first-of-type{display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] > div[data-has-gamepad=false] > div:last-of-type{display:none}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-profile{width:100%;height:36px;display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-note{margin-top:10px;font-size:14px}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row{display:flex;margin-bottom:10px}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row label.bx-prompt{flex:1;font-size:26px;margin-bottom:0}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions{flex:2;position:relative}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions select{position:absolute;width:100%;height:100%;display:block}.bx-settings-tab-contents > div[data-tab-group=shortcuts] .bx-shortcut-row .bx-shortcut-actions select:last-of-type{opacity:0;z-index:calc(var(--bx-settings-z-index) + 1)}.bx-settings-tab-contents .bx-top-buttons{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}.bx-settings-tab-contents .bx-top-buttons .bx-button{display:block}.bx-settings-tab-contents h2{margin:16px 0 8px 0;display:flex;align-items:center}.bx-settings-tab-contents h2:first-of-type{margin-top:0}.bx-settings-tab-contents h2 span{display:inline-block;font-size:20px;font-weight:bold;text-align:left;flex:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}@media (max-width:500px){.bx-settings-tab-contents{width:calc(100vw - 48px)}}.bx-settings-row{display:flex;gap:10px;padding:16px 10px;margin:0;background:#2a2a2a;border-bottom:1px solid #343434}.bx-settings-row:hover,.bx-settings-row:focus-within{background-color:#242424}.bx-settings-row:not(:has(> input[type=checkbox])){flex-wrap:wrap}.bx-settings-row > span.bx-settings-label{font-size:14px;display:block;text-align:left;align-self:center;margin-bottom:0 !important;flex:1}.bx-settings-row > span.bx-settings-label + *{margin:0 0 0 auto}.bx-settings-dialog-note{display:block;color:#afafb0;font-size:12px;font-weight:lighter;font-style:italic}.bx-settings-dialog-note:not(:has(a)){margin-top:4px}.bx-settings-dialog-note a{display:inline-block;padding:4px}.bx-settings-custom-user-agent{display:block;width:100%;padding:6px}.bx-donation-link{display:block;text-align:center;text-decoration:none;height:20px;line-height:20px;font-size:14px;margin-top:10px}.bx-debug-info button{margin-top:10px}.bx-debug-info pre{margin-top:10px;cursor:copy;color:#fff;padding:8px;border:1px solid #2d2d2d;background:#212121;white-space:break-spaces;text-align:left}.bx-debug-info pre:hover{background:#272727}.bx-settings-app-version{margin-top:10px;text-align:center;color:#747474;font-size:12px}.bx-note-unsupported{display:block;font-size:12px;font-style:italic;font-weight:normal;color:#828282}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:has(+ .bx-settings-row){border-top-left-radius:10px;border-top-right-radius:10px}.bx-settings-tab-contents > div .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-bottom-left-radius:10px;border-bottom-right-radius:10px}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-radius:10px}.bx-suggest-toggler{text-align:left;display:flex;border-radius:4px;overflow:hidden;background:#003861}.bx-suggest-toggler label{flex:1;margin-bottom:0;padding:10px;background:#004f87}.bx-suggest-toggler span{display:inline-block;align-self:center;padding:10px;width:40px;text-align:center}.bx-suggest-toggler:hover,.bx-suggest-toggler:focus{cursor:pointer;background:#005da1}.bx-suggest-toggler:hover label,.bx-suggest-toggler:focus label{background:#006fbe}.bx-suggest-toggler[bx-open] span{transform:rotate(90deg)}.bx-suggest-toggler[bx-open]+ .bx-suggest-box{display:block}.bx-suggest-box{display:none;background:#161616;padding:10px;box-shadow:0 0 12px #0f0f0f inset;border-radius:10px}.bx-suggest-wrapper{display:flex;flex-direction:column;gap:10px;margin:10px}.bx-suggest-note{font-size:11px;color:#8c8c8c;font-style:italic;font-weight:100}.bx-suggest-link{font-size:14px;display:inline-block;margin-top:4px;padding:4px}.bx-suggest-row{display:flex;flex-direction:row;gap:10px}.bx-suggest-row label{flex:1;overflow:overlay;border-radius:4px}.bx-suggest-row label .bx-suggest-label{background:#323232;padding:4px 10px;font-size:12px;text-align:left}.bx-suggest-row label .bx-suggest-value{padding:6px;font-size:14px}.bx-suggest-row label .bx-suggest-value.bx-suggest-change{background-color:var(--bx-warning-color)}.bx-suggest-row.bx-suggest-ok input{visibility:hidden}.bx-suggest-row.bx-suggest-ok .bx-suggest-label{background-color:#008114}.bx-suggest-row.bx-suggest-ok .bx-suggest-value{background-color:#13a72a}.bx-suggest-row.bx-suggest-change .bx-suggest-label{background-color:#a65e08}.bx-suggest-row.bx-suggest-change .bx-suggest-value{background-color:#d57f18}.bx-suggest-row.bx-suggest-change:hover label{cursor:pointer}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-label{background-color:#995707}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-value{background-color:#bd7115}.bx-suggest-row.bx-suggest-change input:not(:checked) + label{opacity:.5}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-label{background-color:#2a2a2a}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-value{background-color:#393939}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label{opacity:1}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-label{background-color:#202020}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-value{background-color:#303030}.bx-toast{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:24px;transform:translate(-50%,0);background:#000;border-radius:16px;color:#fff;z-index:var(--bx-toast-z-index);font-family:var(--bx-normal-font);border:2px solid #fff;display:flex;align-items:center;opacity:0;overflow:clip;transition:opacity .2s ease-in}.bx-toast.bx-show{opacity:.85}.bx-toast.bx-hide{opacity:0;pointer-events:none}.bx-toast-msg{font-size:14px;display:inline-block;padding:12px 16px;white-space:pre}.bx-toast-status{font-weight:bold;font-size:14px;text-transform:uppercase;display:inline-block;background:#515863;padding:12px 16px;color:#fff;white-space:pre}.bx-wait-time-box{position:fixed;top:0;right:0;background-color:rgba(0,0,0,0.8);color:#fff;z-index:var(--bx-wait-time-box-z-index);padding:12px;border-radius:0 0 0 8px}.bx-wait-time-box label{display:block;text-transform:uppercase;text-align:right;font-size:12px;font-weight:bold;margin:0}.bx-wait-time-box span{display:block;font-family:var(--bx-monospaced-font);text-align:right;font-size:16px;margin-bottom:10px}.bx-wait-time-box span:last-of-type{margin-bottom:0}.bx-remote-play-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;width:420px;max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:20px}.bx-remote-play-container > .bx-button{display:table;margin:0 0 0 auto}.bx-remote-play-settings{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #2d2d2d}.bx-remote-play-settings > div{display:flex}.bx-remote-play-settings label{flex:1}.bx-remote-play-settings label p{margin:4px 0 0;padding:0;color:#888;font-size:12px}.bx-remote-play-resolution{display:block}.bx-remote-play-resolution input[type="radio"]{accent-color:var(--bx-primary-button-color);margin-right:6px}.bx-remote-play-resolution input[type="radio"]:focus{accent-color:var(--bx-primary-button-hover-color)}.bx-remote-play-device-wrapper{display:flex;margin-bottom:12px}.bx-remote-play-device-wrapper:last-child{margin-bottom:2px}.bx-remote-play-device-info{flex:1;padding:4px 0}.bx-remote-play-device-name{font-size:20px;font-weight:bold;display:inline-block;vertical-align:middle}.bx-remote-play-console-type{font-size:12px;background:#004c87;color:#fff;display:inline-block;border-radius:14px;padding:2px 10px;margin-left:8px;vertical-align:middle}.bx-remote-play-power-state{color:#888;font-size:12px}.bx-remote-play-connect-button{min-height:100%;margin:4px 0}.bx-remote-play-buttons{display:flex;justify-content:space-between}.bx-select{display:flex;align-items:center;flex:0 1 auto}.bx-select select{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-select > div,.bx-select button.bx-select-value{min-width:120px;text-align:left;margin:0 8px;line-height:24px;vertical-align:middle;background:#fff;color:#000;border-radius:4px;padding:2px 8px;flex:1}.bx-select > div{display:inline-block}.bx-select > div input{display:inline-block;margin-right:8px}.bx-select > div label{margin-bottom:0;font-size:14px;width:100%}.bx-select > div label span{display:block;font-size:10px;font-weight:bold;text-align:left;line-height:initial}.bx-select button.bx-select-value{border:none;display:inline-flex;cursor:pointer;min-height:30px;font-size:.9rem;align-items:center}.bx-select button.bx-select-value span{flex:1;text-align:left;display:inline-block}.bx-select button.bx-select-value input{margin:0 4px;accent-color:var(--bx-primary-button-color);pointer-events:none}.bx-select button.bx-select-value:hover input,.bx-select button.bx-select-value:focus input{accent-color:var(--bx-danger-button-color)}.bx-select button.bx-select-value:hover::after,.bx-select button.bx-select-value:focus::after{border-color:#4d4d4d !important}.bx-select button.bx-button{border:none;height:24px;width:24px;padding:0;line-height:24px;color:#fff;border-radius:4px;font-weight:bold;font-size:12px;font-family:var(--bx-monospaced-font);flex-shrink:0}.bx-select button.bx-button span{line-height:unset}.bx-guide-home-achievements-progress{display:flex;gap:10px;flex-direction:row}.bx-guide-home-achievements-progress .bx-button{margin-bottom:0 !important}html[data-xds-platform=tv] .bx-guide-home-achievements-progress{flex-direction:column}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress{flex-direction:row}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:first-of-type{flex:1}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:last-of-type{width:40px}html:not([data-xds-platform=tv]) .bx-guide-home-achievements-progress > button:last-of-type span{display:none}.bx-guide-home-buttons > div{display:flex;flex-direction:row;gap:12px}html[data-xds-platform=tv] .bx-guide-home-buttons > div{flex-direction:column}html[data-xds-platform=tv] .bx-guide-home-buttons > div button{margin-bottom:0 !important}html:not([data-xds-platform=tv]) .bx-guide-home-buttons > div button span{display:none}.bx-guide-home-buttons[data-is-playing="true"] button[data-state='normal']{display:none}.bx-guide-home-buttons[data-is-playing="false"] button[data-state='playing']{display:none}div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]{overflow:visible}.bx-stream-menu-button-on{fill:#000 !important;background-color:#2d2d2d !important;color:#000 !important}.bx-stream-refresh-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important}body[data-media-type=default] .bx-stream-refresh-button{left:calc(env(safe-area-inset-left, 0px) + 11px) !important}body[data-media-type=tv] .bx-stream-refresh-button{top:calc(var(--gds-focus-borderSize) + 80px) !important}.bx-stream-home-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px * 2) !important}body[data-media-type=default] .bx-stream-home-button{left:calc(env(safe-area-inset-left, 0px) + 12px) !important}body[data-media-type=tv] .bx-stream-home-button{top:calc(var(--gds-focus-borderSize) + 80px * 2) !important}div[data-testid=media-container]{display:flex}div[data-testid=media-container].bx-taking-screenshot:before{animation:bx-anim-taking-screenshot .5s ease;content:' ';position:absolute;width:100%;height:100%;z-index:var(--bx-screenshot-animation-z-index)}#game-stream video{margin:auto;align-self:center;background:#000}#game-stream canvas{position:absolute;align-self:center;margin:auto;left:0;right:0}#gamepass-dialog-root div[class^=Guide-module__guide] .bx-button{overflow:visible;margin-bottom:12px}@-moz-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-webkit-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-o-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}.bx-number-stepper{text-align:center}.bx-number-stepper span{display:inline-block;min-width:40px;font-family:var(--bx-monospaced-font);font-size:13px;margin:0 4px}.bx-number-stepper button{border:none;width:24px;height:24px;margin:0;line-height:24px;background-color:var(--bx-default-button-color);color:#fff;border-radius:4px;font-weight:bold;font-size:14px;font-family:var(--bx-monospaced-font)}@media (hover:hover){.bx-number-stepper button:hover{background-color:var(--bx-default-button-hover-color)}}.bx-number-stepper button:active{background-color:var(--bx-default-button-hover-color)}.bx-number-stepper button:disabled + span{font-family:var(--bx-title-font)}.bx-number-stepper input[type="range"]{display:block;margin:12px auto 2px;width:180px;color:#959595 !important}.bx-number-stepper input[type=range]:disabled,.bx-number-stepper button:disabled{display:none}.bx-number-stepper[data-disabled=true] input[type=range],.bx-number-stepper[data-disabled=true] button{display:none}#bx-game-bar{z-index:var(--bx-game-bar-z-index);position:fixed;bottom:0;width:40px;height:90px;overflow:visible;cursor:pointer}#bx-game-bar > svg{display:none;pointer-events:none;position:absolute;height:28px;margin-top:16px}@media (hover:hover){#bx-game-bar:hover > svg{display:block}}#bx-game-bar .bx-game-bar-container{opacity:0;position:absolute;display:flex;overflow:hidden;background:rgba(26,27,30,0.91);box-shadow:0 0 6px #1c1c1c;transition:opacity .1s ease-in}#bx-game-bar .bx-game-bar-container.bx-show{opacity:.9}#bx-game-bar .bx-game-bar-container.bx-show + svg{display:none !important}#bx-game-bar .bx-game-bar-container.bx-hide{opacity:0;pointer-events:none}#bx-game-bar .bx-game-bar-container button{width:60px;height:60px;border-radius:0}#bx-game-bar .bx-game-bar-container button svg{width:28px;height:28px;transition:transform .08s ease 0s}#bx-game-bar .bx-game-bar-container button:hover{border-radius:0}#bx-game-bar .bx-game-bar-container button:active svg{transform:scale(.75)}#bx-game-bar .bx-game-bar-container button.bx-activated{background-color:#fff}#bx-game-bar .bx-game-bar-container button.bx-activated svg{filter:invert(1)}#bx-game-bar .bx-game-bar-container div[data-activated] button{display:none}#bx-game-bar .bx-game-bar-container div[data-activated='false'] button:first-of-type{display:block}#bx-game-bar .bx-game-bar-container div[data-activated='true'] button:last-of-type{display:block}#bx-game-bar[data-position="bottom-left"]{left:0;direction:ltr}#bx-game-bar[data-position="bottom-left"] .bx-game-bar-container{border-radius:0 10px 10px 0}#bx-game-bar[data-position="bottom-right"]{right:0;direction:rtl}#bx-game-bar[data-position="bottom-right"] .bx-game-bar-container{direction:ltr;border-radius:10px 0 0 10px}.bx-badges{margin-left:0;user-select:none;-webkit-user-select:none}.bx-badge{border:none;display:inline-block;line-height:24px;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;font-weight:400;margin:0 8px 8px 0;box-shadow:0 0 6px #000;border-radius:4px}.bx-badge-name{background-color:#2d3036;border-radius:4px 0 0 4px}.bx-badge-name svg{width:16px;height:16px}.bx-badge-value{background-color:#808080;border-radius:0 4px 4px 0}.bx-badge-name,.bx-badge-value{display:inline-block;padding:0 8px;line-height:30px;vertical-align:bottom}.bx-badge-battery[data-charging=true] span:first-of-type::after{content:' ⚡️'}div[class^=StreamMenu-module__container] .bx-badges{position:absolute;max-width:500px}#gamepass-dialog-root .bx-badges{position:fixed;top:60px;left:460px;max-width:500px}@media (min-width:568px) and (max-height:480px){#gamepass-dialog-root .bx-badges{position:unset;top:unset;left:unset;margin:8px 0}}.bx-stats-bar{display:flex;flex-direction:row;gap:8px;user-select:none;-webkit-user-select:none;position:fixed;top:0;background-color:#000;color:#fff;font-family:var(--bx-monospaced-font);font-size:.9rem;padding-left:8px;z-index:var(--bx-stats-bar-z-index);text-wrap:nowrap}.bx-stats-bar[data-stats*="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats*="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats*="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats*="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats*="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats*="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats*="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats*="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats*="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats*="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats*="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats*="[ul]"] > .bx-stat-ul{display:inline-flex;align-items:baseline}.bx-stats-bar[data-stats$="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats$="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats$="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats$="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats$="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats$="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats$="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats$="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats$="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats$="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats$="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats$="[ul]"] > .bx-stat-ul{border-right:none}.bx-stats-bar::before{display:none;content:'👀';vertical-align:middle;margin-right:8px}.bx-stats-bar[data-display=glancing]::before{display:inline-block}.bx-stats-bar[data-position=top-left]{left:0;border-radius:0 0 4px 0}.bx-stats-bar[data-position=top-right]{right:0;border-radius:0 0 0 4px}.bx-stats-bar[data-position=top-center]{transform:translate(-50%,0);left:50%;border-radius:0 0 4px 4px}.bx-stats-bar[data-transparent=true]{background:none;filter:drop-shadow(1px 0 0 rgba(0,0,0,0.941)) drop-shadow(-1px 0 0 rgba(0,0,0,0.941)) drop-shadow(0 1px 0 rgba(0,0,0,0.941)) drop-shadow(0 -1px 0 rgba(0,0,0,0.941))}.bx-stats-bar > div{display:none;border-right:1px solid #fff;padding-right:8px}.bx-stats-bar label{margin:0 8px 0 0;font-family:var(--bx-title-font);font-size:70%;font-weight:bold;vertical-align:middle;cursor:help}.bx-stats-bar span{min-width:60px;display:inline-block;text-align:right;vertical-align:middle}.bx-stats-bar span[data-grade=good]{color:#6bffff}.bx-stats-bar span[data-grade=ok]{color:#fff16b}.bx-stats-bar span[data-grade=bad]{color:#ff5f5f}.bx-stats-bar span:first-of-type{min-width:22px}.bx-mkb-settings{display:flex;flex-direction:column;flex:1;padding-bottom:10px;overflow:hidden}.bx-mkb-settings select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;text-align:right;border:none;color:#fff}.bx-mkb-pointer-lock-msg{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:50%;transform:translateX(-50%) translateY(-50%);margin:auto;background:#151515;z-index:var(--bx-mkb-pointer-lock-msg-z-index);color:#fff;text-align:center;font-weight:400;font-family:"Segoe UI",Arial,Helvetica,sans-serif;font-size:1.3rem;padding:12px;border-radius:8px;align-items:center;box-shadow:0 0 6px #000;min-width:220px;opacity:.9}.bx-mkb-pointer-lock-msg:hover{opacity:1}.bx-mkb-pointer-lock-msg > div:first-of-type{display:flex;flex-direction:column;text-align:left}.bx-mkb-pointer-lock-msg p{margin:0}.bx-mkb-pointer-lock-msg p:first-child{font-size:22px;margin-bottom:4px;font-weight:bold}.bx-mkb-pointer-lock-msg p:last-child{font-size:12px;font-style:italic}.bx-mkb-pointer-lock-msg > div:last-of-type{margin-top:10px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='native'] button:first-of-type{margin-bottom:8px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div{display:flex;flex-flow:row;margin-top:8px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button{flex:1}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button:first-of-type{margin-right:5px}.bx-mkb-pointer-lock-msg > div:last-of-type[data-type='virtual'] div button:last-of-type{margin-left:5px}.bx-mkb-preset-tools{display:flex;margin-bottom:12px}.bx-mkb-preset-tools select{flex:1}.bx-mkb-preset-tools button{margin-left:6px}.bx-mkb-settings-rows{flex:1;overflow:scroll}.bx-mkb-key-row{display:flex;margin-bottom:10px;align-items:center}.bx-mkb-key-row label{margin-bottom:0;font-family:var(--bx-promptfont-font);font-size:26px;text-align:center;width:26px;height:32px;line-height:32px}.bx-mkb-key-row button{flex:1;height:32px;line-height:32px;margin:0 0 0 10px;background:transparent;border:none;color:#fff;border-radius:0;border-left:1px solid #373737}.bx-mkb-key-row button:hover{background:transparent;cursor:default}.bx-mkb-settings.bx-editing .bx-mkb-key-row button{background:#393939;border-radius:4px;border:none}.bx-mkb-settings.bx-editing .bx-mkb-key-row button:hover{background:#333;cursor:pointer}.bx-mkb-action-buttons > div{text-align:right;display:none}.bx-mkb-action-buttons button{margin-left:8px}.bx-mkb-settings:not(.bx-editing) .bx-mkb-action-buttons > div:first-child{display:block}.bx-mkb-settings.bx-editing .bx-mkb-action-buttons > div:last-child{display:block}.bx-mkb-note{display:block;margin:16px 0 10px;font-size:12px}.bx-mkb-note:first-of-type{margin-top:0}.bx-product-details-buttons{display:flex;gap:10px;flex-direction:row}.bx-product-details-buttons button{max-width:max-content;margin:10px 0 0 0;display:flex}@media (min-width:568px) and (max-height:480px){.bx-product-details-buttons{flex-direction:column}.bx-product-details-buttons button{margin:8px 0 0 10px}}`, PREF_HIDE_SECTIONS = getPref("ui_hide_sections"), selectorToHide = []; if (PREF_HIDE_SECTIONS.includes("news")) selectorToHide.push("#BodyContent > div[class*=CarouselRow-module]"); if (PREF_HIDE_SECTIONS.includes("all-games")) selectorToHide.push("#BodyContent div[class*=AllGamesRow-module__gridContainer]"), selectorToHide.push("#BodyContent div[class*=AllGamesRow-module__rowHeader]"); if (PREF_HIDE_SECTIONS.includes("most-popular")) selectorToHide.push('#BodyContent div[class*=HomePage-module__bottomSpacing]:has(a[href="/play/gallery/popular"])'); @@ -6813,11 +6734,11 @@ function addCss() { if (css += "div[class*=StreamMenu-module__menu]{min-width:100vw !important}", getPref("stream_simplify_menu")) css += "div[class*=Menu-module__scrollable]{--bxStreamMenuItemSize:80px;--streamMenuItemSize:calc(var(--bxStreamMenuItemSize) + 40px) !important}.bx-badges{top:calc(var(--streamMenuItemSize) - 20px)}body[data-media-type=tv] .bx-badges{top:calc(var(--streamMenuItemSize) - 10px) !important}button[class*=MenuItem-module__container]{min-width:auto !important;min-height:auto !important;width:var(--bxStreamMenuItemSize) !important;height:var(--bxStreamMenuItemSize) !important}div[class*=MenuItem-module__label]{display:none !important}svg[class*=MenuItem-module__icon]{width:36px;height:100% !important;padding:0 !important;margin:0 !important}"; else css += "body[data-media-type=tv] .bx-badges{top:calc(var(--streamMenuItemSize) + 30px)}body:not([data-media-type=tv]) .bx-badges{top:calc(var(--streamMenuItemSize) + 20px)}body:not([data-media-type=tv]) button[class*=MenuItem-module__container]{min-width:auto !important;width:100px !important}body:not([data-media-type=tv]) button[class*=MenuItem-module__container]:nth-child(n+2){margin-left:10px !important}body:not([data-media-type=tv]) div[class*=MenuItem-module__label]{margin-left:8px !important;margin-right:8px !important}"; if (getPref("ui_scrollbar_hide")) css += "html{scrollbar-width:none}body::-webkit-scrollbar{display:none}"; - const $style = CE("style", {}, css); + let $style = CE("style", {}, css); document.documentElement.appendChild($style); } function preloadFonts() { - const $link = CE("link", { + let $link = CE("link", { rel: "preload", href: "https://redphx.github.io/better-xcloud/fonts/promptfont.otf", as: "font", @@ -6846,7 +6767,7 @@ class MouseCursorHider { } } function patchHistoryMethod(type) { - const orig = window.history[type]; + let orig = window.history[type]; return function(...args) { return BxEvent.dispatch(window, BxEvent.POPSTATE, { arguments: args @@ -6856,10 +6777,11 @@ function patchHistoryMethod(type) { function onHistoryChanged(e) { if (e && e.arguments && e.arguments[0] && e.arguments[0].origin === "better-xcloud") return; window.setTimeout(RemotePlayManager.detect, 10); - const $settings = document.querySelector(".bx-settings-container"); + let $settings = document.querySelector(".bx-settings-container"); if ($settings) $settings.classList.add("bx-gone"); NavigationDialogManager.getInstance().hide(), LoadingScreen.reset(), window.setTimeout(HeaderSection.watchHeader, 2000), BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); } +var LOG_TAG6 = "PreloadState"; function overridePreloadState() { let _state; Object.defineProperty(window, "__PRELOADED_STATE__", { @@ -6874,10 +6796,9 @@ function overridePreloadState() { BxLogger.error(LOG_TAG6, e); } if (STATES.userAgent.capabilities.touch) try { - const sigls = state.xcloud.sigls; + let sigls = state.xcloud.sigls; if ("9c86f07a-f3e8-45ad-82a0-a1f759597059" in sigls) { - let customList = TouchController.getCustomList(); - const allGames = sigls["29a81209-df6f-41fd-a528-2ae6b91f719c"].data.products; + let customList = TouchController.getCustomList(), allGames = sigls["29a81209-df6f-41fd-a528-2ae6b91f719c"].data.products; customList = customList.filter((id2) => allGames.includes(id2)), sigls["9c86f07a-f3e8-45ad-82a0-a1f759597059"]?.data.products.push(...customList); } if (BX_FLAGS.ForceNativeMkbTitles && "8fa264dd-124f-4af3-97e8-596fcdf4b486" in sigls) sigls["8fa264dd-124f-4af3-97e8-596fcdf4b486"]?.data.products.push(...BX_FLAGS.ForceNativeMkbTitles); @@ -6893,29 +6814,27 @@ function overridePreloadState() { } }); } -var LOG_TAG6 = "PreloadState"; function setCodecPreferences(sdp, preferredCodec) { - const h264Pattern = /a=fmtp:(\d+).*profile-level-id=([0-9a-f]{6})/g, profilePrefix = preferredCodec === "high" ? "4d" : preferredCodec === "low" ? "420" : "42e", preferredCodecIds = [], matches = sdp.matchAll(h264Pattern) || []; + let h264Pattern = /a=fmtp:(\d+).*profile-level-id=([0-9a-f]{6})/g, profilePrefix = preferredCodec === "high" ? "4d" : preferredCodec === "low" ? "420" : "42e", preferredCodecIds = [], matches = sdp.matchAll(h264Pattern) || []; for (let match of matches) { - const id2 = match[1]; + let id2 = match[1]; if (match[2].startsWith(profilePrefix)) preferredCodecIds.push(id2); } if (!preferredCodecIds.length) return sdp; - const lines = sdp.split("\r\n"); + let lines = sdp.split("\r\n"); for (let lineIndex = 0;lineIndex < lines.length; lineIndex++) { - const line = lines[lineIndex]; + let line = lines[lineIndex]; if (!line.startsWith("m=video")) continue; - const tmp = line.trim().split(" "); - let ids = tmp.slice(3); + let tmp = line.trim().split(" "), ids = tmp.slice(3); ids = ids.filter((item2) => !preferredCodecIds.includes(item2)), ids = preferredCodecIds.concat(ids), lines[lineIndex] = tmp.slice(0, 3).concat(ids).join(" "); break; } return lines.join("\r\n"); } function patchSdpBitrate(sdp, video, audio) { - const lines = sdp.split("\r\n"), mediaSet = new Set; + let lines = sdp.split("\r\n"), mediaSet = new Set; !!video && mediaSet.add("video"), !!audio && mediaSet.add("audio"); - const bitrate = { + let bitrate = { video, audio }; @@ -6928,7 +6847,7 @@ function patchSdpBitrate(sdp, video, audio) { break; } if (!media) continue; - const bLine = `b=AS:${bitrate[media]}`; + let bLine = `b=AS:${bitrate[media]}`; while (lineNumber++, lineNumber < lines.length) { if (line = lines[lineNumber], line.startsWith("i=") || line.startsWith("c=")) continue; if (line.startsWith("b=AS:")) { @@ -6966,7 +6885,7 @@ class WebGL2Player { animFrameId = null; constructor($video) { BxLogger.info(this.LOG_TAG, "Initialize"), this.$video = $video; - const $canvas = document.createElement("canvas"); + let $canvas = document.createElement("canvas"); $canvas.width = $video.videoWidth, $canvas.height = $video.videoHeight, this.$canvas = $canvas, this.setupShaders(), this.setupRendering(), $video.insertAdjacentElement("afterend", $canvas); } setFilter(filterId, update = !0) { @@ -6991,23 +6910,23 @@ class WebGL2Player { return this.$canvas; } updateCanvas() { - const gl = this.gl, program = this.program; + let gl = this.gl, program = this.program; gl.uniform2f(gl.getUniformLocation(program, "iResolution"), this.$canvas.width, this.$canvas.height), gl.uniform1i(gl.getUniformLocation(program, "filterId"), this.options.filterId), gl.uniform1f(gl.getUniformLocation(program, "sharpenFactor"), this.options.sharpenFactor), gl.uniform1f(gl.getUniformLocation(program, "brightness"), this.options.brightness), gl.uniform1f(gl.getUniformLocation(program, "contrast"), this.options.contrast), gl.uniform1f(gl.getUniformLocation(program, "saturation"), this.options.saturation); } drawFrame() { if (this.targetFps === 0) return; if (this.targetFps < 60) { - const currentTime = performance.now(); + let currentTime = performance.now(); if (currentTime - this.lastFrameTime < this.frameInterval) return; this.lastFrameTime = currentTime; } - const gl = this.gl; + let gl = this.gl; gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, this.$video), gl.drawArrays(gl.TRIANGLES, 0, 6); } setupRendering() { let animate; if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) { - const $video = this.$video; + let $video = this.$video; animate = () => { if (!this.stopped) this.drawFrame(), this.animFrameId = $video.requestVideoFrameCallback(animate); }, this.animFrameId = $video.requestVideoFrameCallback(animate); @@ -7017,23 +6936,23 @@ class WebGL2Player { } setupShaders() { BxLogger.info(this.LOG_TAG, "Setting up", getPref("video_power_preference")); - const gl = this.$canvas.getContext("webgl2", { + let gl = this.$canvas.getContext("webgl2", { isBx: !0, antialias: !0, alpha: !1, powerPreference: getPref("video_power_preference") }); this.gl = gl, gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferWidth); - const vShader = gl.createShader(gl.VERTEX_SHADER); + let vShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vShader, clarity_boost_default), gl.compileShader(vShader); - const fShader = gl.createShader(gl.FRAGMENT_SHADER); + let fShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fShader, clarity_boost_default2), gl.compileShader(fShader); - const program = gl.createProgram(); + let program = gl.createProgram(); if (this.program = program, gl.attachShader(program, vShader), gl.attachShader(program, fShader), gl.linkProgram(program), gl.useProgram(program), !gl.getProgramParameter(program, gl.LINK_STATUS)) console.error(`Link failed: ${gl.getProgramInfoLog(program)}`), console.error(`vs info-log: ${gl.getShaderInfoLog(vShader)}`), console.error(`fs info-log: ${gl.getShaderInfoLog(fShader)}`); this.updateCanvas(); - const buffer = gl.createBuffer(); + let buffer = gl.createBuffer(); this.resources.push(buffer), gl.bindBuffer(gl.ARRAY_BUFFER, buffer), gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW), gl.enableVertexAttribArray(0), gl.vertexAttribPointer(0, 2, gl.FLOAT, !1, 0, 0); - const texture = gl.createTexture(); + let texture = gl.createTexture(); this.resources.push(texture), gl.bindTexture(gl.TEXTURE_2D, texture), gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !0), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR), gl.uniform1i(gl.getUniformLocation(program, "data"), 0), gl.activeTexture(gl.TEXTURE0); } resume() { @@ -7048,7 +6967,7 @@ class WebGL2Player { } destroy() { BxLogger.info(this.LOG_TAG, "Destroy"), this.stop(); - const gl = this.gl; + let gl = this.gl; if (gl) { gl.getExtension("WEBGL_lose_context")?.loseContext(), gl.useProgram(null); for (let resource of this.resources) @@ -7077,9 +6996,9 @@ class StreamPlayer { this.$usmMatrix = this.$videoCss.querySelector("#bx-filter-usm-matrix"); return; } - const $fragment = document.createDocumentFragment(); + let $fragment = document.createDocumentFragment(); this.$videoCss = CE("style", { id: "bx-video-css" }), $fragment.appendChild(this.$videoCss); - const $svg = CE("svg", { + let $svg = CE("svg", { id: "bx-video-filters", xmlns: "http://www.w3.org/2000/svg", class: "bx-gone" @@ -7094,28 +7013,25 @@ class StreamPlayer { $fragment.appendChild($svg), document.documentElement.appendChild($fragment); } getVideoPlayerFilterStyle() { - const filters = [], sharpness = this.options.sharpness || 0; + let filters = [], sharpness = this.options.sharpness || 0; if (this.options.processing === "usm" && sharpness != 0) { - const matrix = `0 -1 0 -1 ${(7 - (sharpness / 2 - 1) * 0.5).toFixed(1)} -1 0 -1 0`; + let matrix = `0 -1 0 -1 ${(7 - (sharpness / 2 - 1) * 0.5).toFixed(1)} -1 0 -1 0`; this.$usmMatrix?.setAttributeNS(null, "kernelMatrix", matrix), filters.push("url(#bx-filter-usm)"); } - const saturation = this.options.saturation || 100; + let saturation = this.options.saturation || 100; if (saturation != 100) filters.push(`saturate(${saturation}%)`); - const contrast = this.options.contrast || 100; + let contrast = this.options.contrast || 100; if (contrast != 100) filters.push(`contrast(${contrast}%)`); - const brightness = this.options.brightness || 100; + let brightness = this.options.brightness || 100; if (brightness != 100) filters.push(`brightness(${brightness}%)`); return filters.join(" "); } resizePlayer() { - const PREF_RATIO = getPref("video_ratio"), $video = this.$video, isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport; - let $webGL2Canvas; + let PREF_RATIO = getPref("video_ratio"), $video = this.$video, isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport, $webGL2Canvas; if (this.playerType == "webgl2") $webGL2Canvas = this.webGL2Player?.getCanvas(); let targetWidth, targetHeight, targetObjectFit; if (PREF_RATIO.includes(":")) { - const tmp = PREF_RATIO.split(":"), videoRatio = parseFloat(tmp[0]) / parseFloat(tmp[1]); - let width = 0, height = 0; - const parentRect = $video.parentElement.getBoundingClientRect(); + let tmp = PREF_RATIO.split(":"), videoRatio = parseFloat(tmp[0]) / parseFloat(tmp[1]), width = 0, height = 0, parentRect = $video.parentElement.getBoundingClientRect(); if (parentRect.width / parentRect.height > videoRatio) height = parentRect.height, width = height * videoRatio; else width = parentRect.width, height = width / videoRatio; width = Math.ceil(Math.min(parentRect.width, width)), height = Math.ceil(Math.min(parentRect.height, height)), $video.dataset.width = width.toString(), $video.dataset.height = height.toString(), targetWidth = `${width}px`, targetHeight = `${height}px`, targetObjectFit = PREF_RATIO === "16:9" ? "contain" : "fill"; @@ -7125,7 +7041,7 @@ class StreamPlayer { } setPlayerType(type, refreshPlayer = !1) { if (this.playerType !== type) { - const videoClass = BX_FLAGS.DeviceInfo.deviceType === "android-tv" ? "bx-pixel" : "bx-gone"; + let videoClass = BX_FLAGS.DeviceInfo.deviceType === "android-tv" ? "bx-pixel" : "bx-gone"; if (type === "webgl2") { if (!this.webGL2Player) this.webGL2Player = new WebGL2Player(this.$video); else this.webGL2Player.resume(); @@ -7150,7 +7066,7 @@ class StreamPlayer { } refreshPlayer() { if (this.playerType === "webgl2") { - const options = this.options, webGL2Player = this.webGL2Player; + let options = this.options, webGL2Player = this.webGL2Player; if (options.processing === "usm") webGL2Player.setFilter(1); else webGL2Player.setFilter(2); Screenshot.updateCanvasFilters("none"), webGL2Player.setSharpness(options.sharpness || 0), webGL2Player.setSaturation(options.saturation || 100), webGL2Player.setContrast(options.contrast || 100), webGL2Player.setBrightness(options.brightness || 100); @@ -7175,9 +7091,9 @@ class StreamPlayer { } } function patchVideoApi() { - const PREF_SKIP_SPLASH_VIDEO = getPref("skip_splash_video"), showFunc = function() { + let PREF_SKIP_SPLASH_VIDEO = getPref("skip_splash_video"), showFunc = function() { if (this.style.visibility = "visible", !this.videoWidth) return; - const playerOptions = { + let playerOptions = { processing: getPref("video_processing"), sharpness: getPref("video_sharpness"), saturation: getPref("video_saturation"), @@ -7193,7 +7109,7 @@ function patchVideoApi() { if (PREF_SKIP_SPLASH_VIDEO) return this.volume = 0, this.style.display = "none", this.dispatchEvent(new Event("ended")), new Promise(() => {}); return nativePlay.apply(this); } - const $parent = this.parentElement; + let $parent = this.parentElement; if (!this.src && $parent.dataset.testid === "media-container") this.addEventListener("loadedmetadata", showFunc, { once: !0 }); return nativePlay.apply(this); }; @@ -7203,16 +7119,16 @@ function patchRtcCodecs() { if (typeof RTCRtpTransceiver === "undefined" || !("setCodecPreferences" in RTCRtpTransceiver.prototype)) return !1; } function patchRtcPeerConnection() { - const nativeCreateDataChannel = RTCPeerConnection.prototype.createDataChannel; + let nativeCreateDataChannel = RTCPeerConnection.prototype.createDataChannel; RTCPeerConnection.prototype.createDataChannel = function() { - const dataChannel = nativeCreateDataChannel.apply(this, arguments); + let dataChannel = nativeCreateDataChannel.apply(this, arguments); return BxEvent.dispatch(window, BxEvent.DATA_CHANNEL_CREATED, { dataChannel }), dataChannel; }; - const maxVideoBitrate = getPref("bitrate_video_max"), codec = getPref("stream_codec_profile"); + let maxVideoBitrate = getPref("bitrate_video_max"), codec = getPref("stream_codec_profile"); if (codec !== "default" || maxVideoBitrate > 0) { - const nativeSetLocalDescription = RTCPeerConnection.prototype.setLocalDescription; + let nativeSetLocalDescription = RTCPeerConnection.prototype.setLocalDescription; RTCPeerConnection.prototype.setLocalDescription = function(description) { if (codec !== "default") arguments[0].sdp = setCodecPreferences(arguments[0].sdp, codec); try { @@ -7223,27 +7139,27 @@ function patchRtcPeerConnection() { return nativeSetLocalDescription.apply(this, arguments); }; } - const OrgRTCPeerConnection = window.RTCPeerConnection; + let OrgRTCPeerConnection = window.RTCPeerConnection; window.RTCPeerConnection = function() { - const conn = new OrgRTCPeerConnection; + let conn = new OrgRTCPeerConnection; return STATES.currentStream.peerConnection = conn, conn.addEventListener("connectionstatechange", (e) => { BxLogger.info("connectionstatechange", conn.connectionState); }), conn; }; } function patchAudioContext() { - const OrgAudioContext = window.AudioContext, nativeCreateGain = OrgAudioContext.prototype.createGain; + let OrgAudioContext = window.AudioContext, nativeCreateGain = OrgAudioContext.prototype.createGain; window.AudioContext = function(options) { if (options && options.latencyHint) options.latencyHint = 0; - const ctx = new OrgAudioContext(options); + let ctx = new OrgAudioContext(options); return BxLogger.info("patchAudioContext", ctx, options), ctx.createGain = function() { - const gainNode = nativeCreateGain.apply(this); + let gainNode = nativeCreateGain.apply(this); return gainNode.gain.value = getPref("audio_volume") / 100, STATES.currentStream.audioGainNode = gainNode, gainNode; }, STATES.currentStream.audioContext = ctx, ctx; }; } function patchMeControl() { - const overrideConfigs = { + let overrideConfigs = { enableAADTelemetry: !1, enableTelemetry: !1, telEvs: "", @@ -7280,7 +7196,7 @@ function disableAdobeAudienceManager() { window.adobe = Object.freeze({}); } function patchCanvasContext() { - const nativeGetContext = HTMLCanvasElement.prototype.getContext; + let nativeGetContext = HTMLCanvasElement.prototype.getContext; HTMLCanvasElement.prototype.getContext = function(contextType, contextAttributes) { if (contextType.includes("webgl")) { if (contextAttributes = contextAttributes || {}, !contextAttributes.isBx) { @@ -7340,7 +7256,7 @@ class TouchControlAction extends BaseGameBarAction { $content; constructor() { super(); - const $btnEnable = createButton({ + let $btnEnable = createButton({ style: 4, icon: BxIcon.TOUCH_CONTROL_ENABLE, title: t("show-touch-controller"), @@ -7356,7 +7272,7 @@ class TouchControlAction extends BaseGameBarAction { } onClick(e) { super.onClick(e); - const isVisible = TouchController.toggleVisibility(); + let isVisible = TouchController.toggleVisibility(); this.$content.dataset.activated = (!isVisible).toString(); } reset() { @@ -7367,7 +7283,7 @@ class MicrophoneAction extends BaseGameBarAction { $content; constructor() { super(); - const $btnDefault = createButton({ + let $btnDefault = createButton({ style: 4, icon: BxIcon.MICROPHONE, onClick: this.onClick.bind(this), @@ -7378,13 +7294,13 @@ class MicrophoneAction extends BaseGameBarAction { onClick: this.onClick.bind(this) }); this.$content = CE("div", {}, $btnMuted, $btnDefault), window.addEventListener(BxEvent.MICROPHONE_STATE_CHANGED, (e) => { - const enabled = e.microphoneState === "Enabled"; + let enabled = e.microphoneState === "Enabled"; this.$content.dataset.activated = enabled.toString(), this.$content.classList.remove("bx-gone"); }); } onClick(e) { super.onClick(e); - const enabled = MicrophoneShortcut.toggle(!1); + let enabled = MicrophoneShortcut.toggle(!1); this.$content.dataset.activated = enabled.toString(); } reset() { @@ -7409,7 +7325,7 @@ class SpeakerAction extends BaseGameBarAction { $content; constructor() { super(); - const $btnEnable = createButton({ + let $btnEnable = createButton({ style: 4, icon: BxIcon.AUDIO, onClick: this.onClick.bind(this) @@ -7420,7 +7336,7 @@ class SpeakerAction extends BaseGameBarAction { classes: ["bx-activated"] }); this.$content = CE("div", {}, $btnEnable, $btnMuted), window.addEventListener(BxEvent.SPEAKER_STATE_CHANGED, (e) => { - const enabled = e.speakerState === 0; + let enabled = e.speakerState === 0; this.$content.dataset.activated = (!enabled).toString(); }); } @@ -7433,10 +7349,10 @@ class SpeakerAction extends BaseGameBarAction { } class RendererShortcut { static toggleVisibility() { - const $mediaContainer = document.querySelector('#game-stream div[data-testid="media-container"]'); + let $mediaContainer = document.querySelector('#game-stream div[data-testid="media-container"]'); if (!$mediaContainer) return !0; $mediaContainer.classList.toggle("bx-gone"); - const isShowing = !$mediaContainer.classList.contains("bx-gone"); + let isShowing = !$mediaContainer.classList.contains("bx-gone"); return limitVideoPlayerFps(isShowing ? getPref("video_max_fps") : 0), isShowing; } } @@ -7444,7 +7360,7 @@ class RendererAction extends BaseGameBarAction { $content; constructor() { super(); - const $btnDefault = createButton({ + let $btnDefault = createButton({ style: 4, icon: BxIcon.EYE, onClick: this.onClick.bind(this) @@ -7458,7 +7374,7 @@ class RendererAction extends BaseGameBarAction { } onClick(e) { super.onClick(e); - const isVisible = RendererShortcut.toggleVisibility(); + let isVisible = RendererShortcut.toggleVisibility(); this.$content.dataset.activated = (!isVisible).toString(); } reset() { @@ -7474,8 +7390,7 @@ class GameBar { timeoutId = null; actions = []; constructor() { - let $container; - const position = getPref("game_bar_position"), $gameBar = CE("div", { id: "bx-game-bar", class: "bx-gone", "data-position": position }, $container = CE("div", { class: "bx-game-bar-container bx-offscreen" }), createSvgIcon(position === "bottom-left" ? BxIcon.CARET_RIGHT : BxIcon.CARET_LEFT)); + let $container, position = getPref("game_bar_position"), $gameBar = CE("div", { id: "bx-game-bar", class: "bx-gone", "data-position": position }, $container = CE("div", { class: "bx-game-bar-container bx-offscreen" }), createSvgIcon(position === "bottom-left" ? BxIcon.CARET_RIGHT : BxIcon.CARET_LEFT)); if (this.actions = [ new ScreenshotAction, ...STATES.userAgent.capabilities.touch && getPref("stream_touch_controller") !== "off" ? [new TouchControlAction] : [], @@ -7531,7 +7446,7 @@ class XcloudApi { CACHE_WAIT_TIME = {}; async getTitleInfo(id2) { if (id2 in this.CACHE_TITLES) return this.CACHE_TITLES[id2]; - const baseUri = STATES.selectedRegion.baseUri; + let baseUri = STATES.selectedRegion.baseUri; if (!baseUri || !STATES.gsToken) return null; let json; try { @@ -7553,7 +7468,7 @@ class XcloudApi { } async getWaitTime(id2) { if (id2 in this.CACHE_WAIT_TIME) return this.CACHE_WAIT_TIME[id2]; - const baseUri = STATES.selectedRegion.baseUri; + let baseUri = STATES.selectedRegion.baseUri; if (!baseUri || !STATES.gsToken) return null; let json; try { @@ -7574,14 +7489,13 @@ class GameTile { static async#showWaitTime($elm, productId) { if ($elm.hasWaitTime) return; $elm.hasWaitTime = !0; - let totalWaitTime; - const api = XcloudApi.getInstance(), info = await api.getTitleInfo(productId); + let totalWaitTime, api = XcloudApi.getInstance(), info = await api.getTitleInfo(productId); if (info) { - const waitTime = await api.getWaitTime(info.titleId); + let waitTime = await api.getWaitTime(info.titleId); if (waitTime) totalWaitTime = waitTime.estimatedAllocationTimeInSeconds; } if (typeof totalWaitTime === "number" && isElementVisible($elm)) { - const $div = CE("div", { class: "bx-game-tile-wait-time" }, createSvgIcon(BxIcon.PLAYTIME), CE("span", {}, secondsToHms(totalWaitTime))); + let $div = CE("div", { class: "bx-game-tile-wait-time" }, createSvgIcon(BxIcon.PLAYTIME), CE("span", {}, secondsToHms(totalWaitTime))); $elm.insertAdjacentElement("afterbegin", $div); } } @@ -7607,15 +7521,15 @@ class GameTile { } static setup() { window.addEventListener(BxEvent.NAVIGATION_FOCUS_CHANGED, (e) => { - const $elm = e.element; + let $elm = e.element; if (($elm.className || "").includes("MruGameCard")) { - const $ol = $elm.closest("ol"); + let $ol = $elm.closest("ol"); if ($ol && !$ol.hasWaitTime) $ol.hasWaitTime = !0, $ol.querySelectorAll("button[class*=MruGameCard]").forEach(($elm2) => { - const productId = GameTile.#findProductId($elm2); + let productId = GameTile.#findProductId($elm2); productId && GameTile.#showWaitTime($elm2, productId); }); } else { - const productId = GameTile.#findProductId($elm); + let productId = GameTile.#findProductId($elm); productId && GameTile.#requestWaitTime($elm, productId); } }); @@ -7638,9 +7552,9 @@ class ProductDetailsPage { tabIndex: 0, onClick: async (e) => { try { - const matches = /\/games\/(?[^\/]+)\/(?\w+)/.exec(window.location.pathname); + let matches = /\/games\/(?[^\/]+)\/(?\w+)/.exec(window.location.pathname); if (!matches?.groups) return; - const titleSlug = matches.groups.titleSlug.replaceAll("%" + "7C", "-"), productId = matches.groups.productId; + let titleSlug = matches.groups.titleSlug.replaceAll("%" + "7C", "-"), productId = matches.groups.productId; AppInterface.downloadWallpapers(titleSlug, productId); } catch (e2) {} } @@ -7649,7 +7563,7 @@ class ProductDetailsPage { static injectButtons() { if (!AppInterface) return; ProductDetailsPage.injectTimeoutId && clearTimeout(ProductDetailsPage.injectTimeoutId), ProductDetailsPage.injectTimeoutId = window.setTimeout(() => { - const $container = document.querySelector("div[class*=ActionButtons-module__container]"); + 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)); @@ -7664,18 +7578,17 @@ class StreamUiHandler { static observer; static cloneStreamHudButton($btnOrg, label, svgIcon) { if (!$btnOrg) return null; - const $container = $btnOrg.cloneNode(!0); - let timeout; + let $container = $btnOrg.cloneNode(!0), timeout; if (STATES.browser.capabilities.touch) { - const onTransitionStart = (e) => { + let onTransitionStart = (e) => { if (e.propertyName !== "opacity") return; timeout && clearTimeout(timeout), e.target.style.pointerEvents = "none"; }, onTransitionEnd = (e) => { if (e.propertyName !== "opacity") return; - const $streamHud = e.target.closest("#StreamHud"); + let $streamHud = e.target.closest("#StreamHud"); if (!$streamHud) return; if ($streamHud.style.left === "0px") { - const $target = e.target; + let $target = e.target; timeout && clearTimeout(timeout), timeout = window.setTimeout(() => { $target.style.pointerEvents = "auto"; }, 100); @@ -7683,21 +7596,21 @@ class StreamUiHandler { }; $container.addEventListener("transitionstart", onTransitionStart), $container.addEventListener("transitionend", onTransitionEnd); } - const $button = $container.querySelector("button"); + let $button = $container.querySelector("button"); if (!$button) return null; $button.setAttribute("title", label); - const $orgSvg = $button.querySelector("svg"); + let $orgSvg = $button.querySelector("svg"); if (!$orgSvg) return null; - const $svg = createSvgIcon(svgIcon); + let $svg = createSvgIcon(svgIcon); return $svg.style.fill = "none", $svg.setAttribute("class", $orgSvg.getAttribute("class") || ""), $svg.ariaHidden = "true", $orgSvg.replaceWith($svg), $container; } static cloneCloseButton($btnOrg, icon, className, onChange) { if (!$btnOrg) return null; - const $btn = $btnOrg.cloneNode(!0), $svg = createSvgIcon(icon); + let $btn = $btnOrg.cloneNode(!0), $svg = createSvgIcon(icon); return $svg.setAttribute("class", $btn.firstElementChild.getAttribute("class") || ""), $svg.style.fill = "none", $btn.classList.add(className), $btn.removeChild($btn.firstElementChild), $btn.appendChild($svg), $btn.addEventListener("click", onChange), $btn; } static async handleStreamMenu() { - const $btnCloseHud = document.querySelector("button[class*=StreamMenu-module__backButton]"); + let $btnCloseHud = document.querySelector("button[class*=StreamMenu-module__backButton]"); if (!$btnCloseHud) return; let { $btnRefresh, $btnHome } = StreamUiHandler; if (typeof $btnRefresh === "undefined") $btnRefresh = StreamUiHandler.cloneCloseButton($btnCloseHud, BxIcon.REFRESH, "bx-stream-refresh-button", () => { @@ -7710,29 +7623,27 @@ class StreamUiHandler { document.querySelector("div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]")?.appendChild(await StreamBadges.getInstance().render()); } static handleSystemMenu($streamHud) { - const $orgButton = $streamHud.querySelector("div[class^=HUDButton]"); + let $orgButton = $streamHud.querySelector("div[class^=HUDButton]"); if (!$orgButton) return; - const hideGripHandle = () => { - const $gripHandle = document.querySelector("#StreamHud button[class^=GripHandle]"); + let hideGripHandle = () => { + let $gripHandle = document.querySelector("#StreamHud button[class^=GripHandle]"); if ($gripHandle && $gripHandle.ariaExpanded === "true") $gripHandle.dispatchEvent(new PointerEvent("pointerdown")), $gripHandle.click(), $gripHandle.dispatchEvent(new PointerEvent("pointerdown")), $gripHandle.click(); - }; - let $btnStreamSettings = StreamUiHandler.$btnStreamSettings; + }, $btnStreamSettings = StreamUiHandler.$btnStreamSettings; if (typeof $btnStreamSettings === "undefined") $btnStreamSettings = StreamUiHandler.cloneStreamHudButton($orgButton, t("better-xcloud"), BxIcon.BETTER_XCLOUD), $btnStreamSettings?.addEventListener("click", (e) => { hideGripHandle(), e.preventDefault(), SettingsNavigationDialog.getInstance().show(); }), StreamUiHandler.$btnStreamSettings = $btnStreamSettings; - const streamStats = StreamStats.getInstance(); - let $btnStreamStats = StreamUiHandler.$btnStreamStats; + let streamStats = StreamStats.getInstance(), $btnStreamStats = StreamUiHandler.$btnStreamStats; if (typeof $btnStreamStats === "undefined") $btnStreamStats = StreamUiHandler.cloneStreamHudButton($orgButton, t("stream-stats"), BxIcon.STREAM_STATS), $btnStreamStats?.addEventListener("click", async (e) => { hideGripHandle(), e.preventDefault(), await streamStats.toggle(); - const btnStreamStatsOn = !streamStats.isHidden() && !streamStats.isGlancing(); + let btnStreamStatsOn = !streamStats.isHidden() && !streamStats.isGlancing(); $btnStreamStats.classList.toggle("bx-stream-menu-button-on", btnStreamStatsOn); }), StreamUiHandler.$btnStreamStats = $btnStreamStats; - const $btnParent = $orgButton.parentElement; + let $btnParent = $orgButton.parentElement; if ($btnStreamSettings && $btnStreamStats) { - const btnStreamStatsOn = !streamStats.isHidden() && !streamStats.isGlancing(); + let btnStreamStatsOn = !streamStats.isHidden() && !streamStats.isGlancing(); $btnStreamStats.classList.toggle("bx-stream-menu-button-on", btnStreamStatsOn), $btnParent.insertBefore($btnStreamStats, $btnParent.lastElementChild), $btnParent.insertBefore($btnStreamSettings, $btnStreamStats); } - const $dotsButton = $btnParent.lastElementChild; + let $dotsButton = $btnParent.lastElementChild; $dotsButton.parentElement.insertBefore($dotsButton, $dotsButton.parentElement.firstElementChild); } static reset() { @@ -7740,16 +7651,16 @@ class StreamUiHandler { } static observe() { StreamUiHandler.reset(); - const $screen = document.querySelector("#PageContent section[class*=PureScreens]"); + let $screen = document.querySelector("#PageContent section[class*=PureScreens]"); if (!$screen) return; - const observer = new MutationObserver((mutationList) => { + let observer = new MutationObserver((mutationList) => { mutationList.forEach((item2) => { if (item2.type !== "childList") return; item2.addedNodes.forEach(async ($node) => { if (!$node || $node.nodeType !== Node.ELEMENT_NODE) return; let $elm = $node; if (!($elm instanceof HTMLElement)) return; - const className = $elm.className || ""; + let className = $elm.className || ""; if (className.includes("PureErrorPage")) { BxEvent.dispatch(window, BxEvent.STREAM_ERROR_PAGE); return; @@ -7772,57 +7683,16 @@ class XboxApi { static async getProductTitle(xboxTitleId) { if (xboxTitleId = xboxTitleId.toString(), XboxApi.CACHED_TITLES[xboxTitleId]) return XboxApi.CACHED_TITLES[xboxTitleId]; try { - const url = `https://displaycatalog.mp.microsoft.com/v7.0/products/lookup?market=US&languages=en&value=${xboxTitleId}&alternateId=XboxTitleId&fieldsTemplate=browse`, productTitle = (await (await NATIVE_FETCH(url)).json()).Products[0].LocalizedProperties[0].ProductTitle; + let url = `https://displaycatalog.mp.microsoft.com/v7.0/products/lookup?market=US&languages=en&value=${xboxTitleId}&alternateId=XboxTitleId&fieldsTemplate=browse`, productTitle = (await (await NATIVE_FETCH(url)).json()).Products[0].LocalizedProperties[0].ProductTitle; return XboxApi.CACHED_TITLES[xboxTitleId] = productTitle, productTitle; } catch (e) {} return null; } } -function unload() { - if (!STATES.isPlaying) return; - EmulatedMkbHandler.getInstance().destroy(), NativeMkbHandler.getInstance().destroy(), STATES.currentStream.streamPlayer?.destroy(), STATES.isPlaying = !1, STATES.currentStream = {}, window.BX_EXPOSED.shouldShowSensorControls = !1, window.BX_EXPOSED.stopTakRendering = !1, NavigationDialogManager.getInstance().hide(), StreamStats.getInstance().onStoppedPlaying(), MouseCursorHider.stop(), TouchController.reset(), GameBar.getInstance().disable(); -} -function observeRootDialog($root) { - let beingShown = !1; - new MutationObserver((mutationList) => { - for (let mutation of mutationList) { - if (mutation.type !== "childList") continue; - if (BX_FLAGS.Debug && BxLogger.warning("RootDialog", "added", mutation.addedNodes), mutation.addedNodes.length === 1) { - const $addedElm = mutation.addedNodes[0]; - if ($addedElm instanceof HTMLElement && $addedElm.className) { - if ($root.querySelector("div[class*=GuideDialog]")) GuideMenu.observe($addedElm); - } - } - const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0); - if (shown !== beingShown) beingShown = shown, BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_DIALOG_DISMISSED); - } - }).observe($root, { subtree: !0, childList: !0 }); -} -function waitForRootDialog() { - const observer = new MutationObserver((mutationList) => { - for (let mutation of mutationList) { - if (mutation.type !== "childList") continue; - const $target = mutation.target; - if ($target.id && $target.id === "gamepass-dialog-root") { - observer.disconnect(), observeRootDialog($target); - break; - } - } - }); - observer.observe(document.documentElement, { subtree: !0, childList: !0 }); -} -function main() { - if (getPref("game_msfs2020_force_native_mkb")) BX_FLAGS.ForceNativeMkbTitles.push("9PMQDM08SNK9"); - if (patchRtcPeerConnection(), patchRtcCodecs(), interceptHttpRequests(), patchVideoApi(), patchCanvasContext(), AppInterface && patchPointerLockApi(), getPref("audio_enable_volume_control") && patchAudioContext(), getPref("block_tracking")) patchMeControl(), disableAdobeAudienceManager(); - if (waitForRootDialog(), addCss(), Toast.setup(), GuideMenu.addEventListeners(), StreamStatsCollector.setupEvents(), StreamBadges.setupEvents(), StreamStats.setupEvents(), getPref("game_bar_position") !== "off" && GameBar.getInstance(), Screenshot.setup(), STATES.userAgent.capabilities.touch && TouchController.updateCustomList(), overridePreloadState(), VibrationManager.initialSetup(), BX_FLAGS.CheckForUpdate && checkForUpdate(), Patcher.init(), disablePwa(), getPref("xhome_enabled")) RemotePlayManager.detect(); - if (getPref("stream_touch_controller") === "all") TouchController.setup(); - if (getPref("mkb_enabled") && AppInterface) STATES.pointerServerPort = AppInterface.startPointerServer() || 9269, BxLogger.info("startPointerServer", "Port", STATES.pointerServerPort.toString()); - if (getPref("ui_game_card_show_wait_time") && GameTile.setup(), EmulatedMkbHandler.setupEvents(), getPref("controller_show_connection_status")) window.addEventListener("gamepadconnected", (e) => showGamepadToast(e.gamepad)), window.addEventListener("gamepaddisconnected", (e) => showGamepadToast(e.gamepad)); -} if (window.location.pathname.includes("/auth/msa")) { - const nativePushState = window.history.pushState; + let nativePushState = window.history.pushState; throw window.history.pushState = function(...args) { - const url = args[2]; + let url = args[2]; if (url && (url.startsWith("/play") || url.substring(6).startsWith("/play"))) { console.log("Redirecting to xbox.com/play"), window.stop(), window.location.href = "https://www.xbox.com" + url; return; @@ -7835,14 +7705,13 @@ if (BX_FLAGS.SafariWorkaround && document.readyState !== "loading") { window.stop(); let css = ""; css += '.bx-reload-overlay{position:fixed;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;background:rgba(0,0,0,0.8);z-index:9999;color:#fff;text-align:center;font-weight:400;font-family:"Segoe UI",Arial,Helvetica,sans-serif;font-size:1.3rem}.bx-reload-overlay *:focus{outline:none !important}.bx-reload-overlay > div{margin:0 auto}.bx-reload-overlay a{text-decoration:none;display:inline-block;background:#107c10;color:#fff;border-radius:4px;padding:6px}'; - const isSafari = UserAgent.isSafari(); - let $secondaryAction; + let isSafari = UserAgent.isSafari(), $secondaryAction; if (isSafari) $secondaryAction = CE("p", {}, t("settings-reloading")); else $secondaryAction = CE("a", { href: "https://better-xcloud.github.io/troubleshooting", target: "_blank" }, "🤓 " + t("how-to-fix")); - const $fragment = document.createDocumentFragment(); + let $fragment = document.createDocumentFragment(); throw $fragment.appendChild(CE("style", {}, css)), $fragment.appendChild(CE("div", { class: "bx-reload-overlay" }, CE("div", {}, CE("p", {}, t("load-failed-message")), $secondaryAction))), document.documentElement.appendChild($fragment), isSafari && window.location.reload(!0), new Error("[Better xCloud] Executing workaround for Safari"); @@ -7857,7 +7726,7 @@ document.addEventListener("readystatechange", (e) => { if (STATES.isSignedIn = !!window.xbcUser?.isSignedIn, STATES.isSignedIn) getPref("xhome_enabled") && RemotePlayManager.getInstance().initialize(); else window.setTimeout(HeaderSection.watchHeader, 2000); if (getPref("ui_hide_sections").includes("friends")) { - const $parent = document.querySelector("div[class*=PlayWithFriendsSkeleton]")?.closest("div[class*=HomePage-module]"); + let $parent = document.querySelector("div[class*=PlayWithFriendsSkeleton]")?.closest("div[class*=HomePage-module]"); $parent && ($parent.style.display = "none"); } preloadFonts(); @@ -7883,11 +7752,11 @@ window.addEventListener(BxEvent.STREAM_STARTING, (e) => { }); window.addEventListener(BxEvent.STREAM_PLAYING, (e) => { if (STATES.isPlaying = !0, StreamUiHandler.observe(), getPref("game_bar_position") !== "off") { - const gameBar = GameBar.getInstance(); + let gameBar = GameBar.getInstance(); gameBar.reset(), gameBar.enable(), gameBar.showBar(); } { - const $video = e.$video; + let $video = e.$video; Screenshot.updateCanvasSize($video.videoWidth, $video.videoHeight); } updateVideoPlayer(); @@ -7899,21 +7768,25 @@ window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, (e) => { if (e.component === "product-details") ProductDetailsPage.injectButtons(); }); window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => { - const dataChannel = e.dataChannel; + let dataChannel = e.dataChannel; if (!dataChannel || dataChannel.label !== "message") return; dataChannel.addEventListener("message", async (msg) => { if (msg.origin === "better-xcloud" || typeof msg.data !== "string") return; if (msg.data.includes("/titleinfo")) { - const json = JSON.parse(JSON.parse(msg.data).content), xboxTitleId = parseInt(json.titleid, 16); + let json = JSON.parse(JSON.parse(msg.data).content), xboxTitleId = parseInt(json.titleid, 16); if (STATES.currentStream.xboxTitleId = xboxTitleId, STATES.remotePlay.isPlaying) { if (STATES.currentStream.titleSlug = "remote-play", json.focused) { - const productTitle = await XboxApi.getProductTitle(xboxTitleId); + let productTitle = await XboxApi.getProductTitle(xboxTitleId); if (productTitle) STATES.currentStream.titleSlug = productTitleToSlug(productTitle); } } } }); }); +function unload() { + if (!STATES.isPlaying) return; + EmulatedMkbHandler.getInstance().destroy(), NativeMkbHandler.getInstance().destroy(), STATES.currentStream.streamPlayer?.destroy(), STATES.isPlaying = !1, STATES.currentStream = {}, window.BX_EXPOSED.shouldShowSensorControls = !1, window.BX_EXPOSED.stopTakRendering = !1, NavigationDialogManager.getInstance().hide(), StreamStats.getInstance().onStoppedPlaying(), MouseCursorHider.stop(), TouchController.reset(), GameBar.getInstance().disable(); +} window.addEventListener(BxEvent.STREAM_STOPPED, unload); window.addEventListener("pagehide", (e) => { BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); @@ -7921,4 +7794,41 @@ window.addEventListener("pagehide", (e) => { window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, (e) => { Screenshot.takeScreenshot(); }); +function observeRootDialog($root) { + let beingShown = !1; + new MutationObserver((mutationList) => { + for (let mutation of mutationList) { + if (mutation.type !== "childList") continue; + if (BX_FLAGS.Debug && BxLogger.warning("RootDialog", "added", mutation.addedNodes), mutation.addedNodes.length === 1) { + let $addedElm = mutation.addedNodes[0]; + if ($addedElm instanceof HTMLElement && $addedElm.className) { + if ($root.querySelector("div[class*=GuideDialog]")) GuideMenu.observe($addedElm); + } + } + let shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0); + if (shown !== beingShown) beingShown = shown, BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_DIALOG_DISMISSED); + } + }).observe($root, { subtree: !0, childList: !0 }); +} +function waitForRootDialog() { + let observer = new MutationObserver((mutationList) => { + for (let mutation of mutationList) { + if (mutation.type !== "childList") continue; + let $target = mutation.target; + if ($target.id && $target.id === "gamepass-dialog-root") { + observer.disconnect(), observeRootDialog($target); + break; + } + } + }); + observer.observe(document.documentElement, { subtree: !0, childList: !0 }); +} +function main() { + if (getPref("game_msfs2020_force_native_mkb")) BX_FLAGS.ForceNativeMkbTitles.push("9PMQDM08SNK9"); + if (patchRtcPeerConnection(), patchRtcCodecs(), interceptHttpRequests(), patchVideoApi(), patchCanvasContext(), AppInterface && patchPointerLockApi(), getPref("audio_enable_volume_control") && patchAudioContext(), getPref("block_tracking")) patchMeControl(), disableAdobeAudienceManager(); + if (waitForRootDialog(), addCss(), Toast.setup(), GuideMenu.addEventListeners(), StreamStatsCollector.setupEvents(), StreamBadges.setupEvents(), StreamStats.setupEvents(), getPref("game_bar_position") !== "off" && GameBar.getInstance(), Screenshot.setup(), STATES.userAgent.capabilities.touch && TouchController.updateCustomList(), overridePreloadState(), VibrationManager.initialSetup(), BX_FLAGS.CheckForUpdate && checkForUpdate(), Patcher.init(), disablePwa(), getPref("xhome_enabled")) RemotePlayManager.detect(); + if (getPref("stream_touch_controller") === "all") TouchController.setup(); + if (getPref("mkb_enabled") && AppInterface) STATES.pointerServerPort = AppInterface.startPointerServer() || 9269, BxLogger.info("startPointerServer", "Port", STATES.pointerServerPort.toString()); + if (getPref("ui_game_card_show_wait_time") && GameTile.setup(), EmulatedMkbHandler.setupEvents(), getPref("controller_show_connection_status")) window.addEventListener("gamepadconnected", (e) => showGamepadToast(e.gamepad)), window.addEventListener("gamepaddisconnected", (e) => showGamepadToast(e.gamepad)); +} main(); diff --git a/package.json b/package.json index 946c735..149738d 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,14 @@ "build": "build.ts" }, "devDependencies": { - "@types/bun": "^1.1.10", - "@types/node": "^22.7.5", + "@types/bun": "^1.1.11", + "@types/node": "^22.7.6", "@types/stylus": "^0.48.43", "eslint": "^9.12.0", "eslint-plugin-compat": "^6.0.1", "stylus": "^0.63.0" }, "peerDependencies": { - "typescript": "^5.6.2" + "typescript": "^5.6.3" } }