From bd58355ef5a21e85311affb22e064cb8b5115258 Mon Sep 17 00:00:00 2001 From: redphx <96280+redphx@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:11:05 +0700 Subject: [PATCH] Create better-xcloud.lite.user.js --- build.ts | 47 ++++++-- package.json | 1 + src/assets/header_script.lite.txt | 13 +++ src/index.ts | 101 ++++++++++-------- src/macros/build.ts | 8 ++ src/modules/stream-player.ts | 6 +- src/modules/stream/stream-badges.ts | 23 ++-- src/modules/ui/dialog/settings-dialog.ts | 51 ++++++++- src/modules/ui/guide-menu.ts | 39 +++++-- src/types/index.d.ts | 4 +- src/types/setting-definition.d.ts | 1 + src/utils/bx-exposed.ts | 6 +- src/utils/global.ts | 1 + src/utils/screenshot.ts | 2 +- .../base-settings-storage.ts | 17 ++- .../global-settings-storage.ts | 34 +++++- src/utils/true-achievements.ts | 14 ++- 17 files changed, 276 insertions(+), 92 deletions(-) create mode 100644 src/assets/header_script.lite.txt diff --git a/build.ts b/build.ts index 0959b05..50043b0 100644 --- a/build.ts +++ b/build.ts @@ -5,6 +5,8 @@ import { sys } from "typescript"; // @ts-ignore import txtScriptHeader from "./src/assets/header_script.txt" with { type: "text" }; // @ts-ignore +import txtScriptHeaderLite from "./src/assets/header_script.lite.txt" with { type: "text" }; +// @ts-ignore import txtMetaHeader from "./src/assets/header_meta.txt" with { type: "text" }; import { assert } from "node:console"; import { ESLint } from "eslint"; @@ -16,6 +18,8 @@ enum BuildTarget { WEBOS = 'webos', } +type BuildVariant = 'full' | 'lite'; + const postProcess = (str: string): string => { // Unescape unicode charaters str = unescape((str.replace(/\\u/g, '%u'))); @@ -80,7 +84,7 @@ const postProcess = (str: string): string => { return str; } -const build = async (target: BuildTarget, version: string, config: any={}) => { +const build = async (target: BuildTarget, version: string, variant: BuildVariant, config: any={}) => { console.log('-- Target:', target); const startTime = performance.now(); @@ -88,6 +92,11 @@ const build = async (target: BuildTarget, version: string, config: any={}) => { if (target !== BuildTarget.ALL) { outputScriptName += `.${target}`; } + + if (variant !== 'full') { + outputScriptName += `.${variant}`; + } + let outputMetaName = outputScriptName; outputScriptName += '.user.js'; outputMetaName += '.meta.js'; @@ -103,6 +112,7 @@ const build = async (target: BuildTarget, version: string, config: any={}) => { }, define: { 'Bun.env.BUILD_TARGET': JSON.stringify(target), + 'Bun.env.BUILD_VARIANT': JSON.stringify(variant), 'Bun.env.SCRIPT_VERSION': JSON.stringify(version), }, }); @@ -117,7 +127,13 @@ const build = async (target: BuildTarget, version: string, config: any={}) => { let result = postProcess(await readFile(path, 'utf-8')); // Replace [[VERSION]] with real value - const scriptHeader = txtScriptHeader.replace('[[VERSION]]', version); + let scriptHeader: string; + if (variant === 'full') { + scriptHeader = txtScriptHeader; + } else { + scriptHeader = txtScriptHeaderLite; + } + scriptHeader = scriptHeader.replace('[[VERSION]]', version); // Save to script await Bun.write(path, scriptHeader + result); @@ -148,25 +164,40 @@ const buildTargets = [ const { values, positionals } = parseArgs({ args: Bun.argv, options: { - version: { - type: 'string', + version: { + type: 'string', + }, - }, + variant: { + type: 'string', + default: 'full', + }, }, strict: true, allowPositionals: true, - }); +}) as { + values: { + version: string, + variant: BuildVariant, + }, + positionals: string[], +}; if (!values['version']) { console.log('Missing --version param'); sys.exit(-1); } +if (values['variant'] !== 'full' && values['variant'] !== 'lite') { + console.log('--variant param must be either "full" or "lite"'); + sys.exit(-1); +} + async function main() { const config = {}; - console.log('Building: ', values['version']); + console.log(`Building: VERSION=${values['version']}, VARIANT=${values['variant']}`); for (const target of buildTargets) { - await build(target, values['version']!!, config); + await build(target, values['version']!!, values['variant'], config); } console.log('\n** Press Enter to build or Esc to exit'); diff --git a/package.json b/package.json index 1d77320..523f1bf 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "better-xcloud", "module": "src/index.ts", "type": "module", + "sideEffects": false, "browserslist": [ "Chrome >= 80" ], diff --git a/src/assets/header_script.lite.txt b/src/assets/header_script.lite.txt new file mode 100644 index 0000000..bb745ca --- /dev/null +++ b/src/assets/header_script.lite.txt @@ -0,0 +1,13 @@ +// ==UserScript== +// @name Better xCloud (Lite) +// @namespace https://github.com/redphx +// @version [[VERSION]] +// @description Improve Xbox Cloud Gaming (xCloud) experience +// @author redphx +// @license MIT +// @match https://www.xbox.com/*/play* +// @match https://www.xbox.com/*/auth/msa?*loggedIn* +// @run-at document-end +// @grant none +// ==/UserScript== +"use strict"; diff --git a/src/index.ts b/src/index.ts index daf2688..1286fd2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ +import { compressCss, isFullVersion } from "@macros/build" with {type: "macro"}; + import "@utils/global"; import { BxEvent } from "@utils/bx-event"; import { BX_FLAGS } from "@utils/bx-flags"; @@ -35,13 +37,11 @@ import { ProductDetailsPage } from "./modules/ui/product-details"; import { NavigationDialogManager } from "./modules/ui/dialog/navigation-dialog"; import { PrefKey } from "./enums/pref-keys"; import { getPref, StreamTouchController } from "./utils/settings-storages/global-settings-storage"; -import { compressCss } from "@macros/build" with {type: "macro"}; import { SettingsNavigationDialog } from "./modules/ui/dialog/settings-dialog"; import { StreamUiHandler } from "./modules/stream/stream-ui"; import { UserAgent } from "./utils/user-agent"; import { XboxApi } from "./utils/xbox-api"; - // Handle login page if (window.location.pathname.includes('/auth/msa')) { const nativePushState = window.history['pushState']; @@ -63,7 +63,7 @@ if (window.location.pathname.includes('/auth/msa')) { BxLogger.info('readyState', document.readyState); -if (BX_FLAGS.SafariWorkaround && document.readyState !== 'loading') { +if (isFullVersion() && BX_FLAGS.SafariWorkaround && document.readyState !== 'loading') { // Stop loading window.stop(); @@ -227,15 +227,17 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => { STATES.isPlaying = true; StreamUiHandler.observe(); - if (getPref(PrefKey.GAME_BAR_POSITION) !== 'off') { + if (isFullVersion() && getPref(PrefKey.GAME_BAR_POSITION) !== 'off') { const gameBar = GameBar.getInstance(); gameBar.reset(); gameBar.enable(); gameBar.showBar(); } - const $video = (e as any).$video as HTMLVideoElement; - Screenshot.updateCanvasSize($video.videoWidth, $video.videoHeight); + if (isFullVersion()) { + const $video = (e as any).$video as HTMLVideoElement; + Screenshot.updateCanvasSize($video.videoWidth, $video.videoHeight); + } updateVideoPlayer(); }); @@ -288,9 +290,11 @@ function unload() { return; } - // Stop MKB listeners - EmulatedMkbHandler.getInstance().destroy(); - NativeMkbHandler.getInstance().destroy(); + if (isFullVersion()) { + // Stop MKB listeners + EmulatedMkbHandler.getInstance().destroy(); + NativeMkbHandler.getInstance().destroy(); + } // Destroy StreamPlayer STATES.currentStream.streamPlayer?.destroy(); @@ -303,9 +307,11 @@ function unload() { NavigationDialogManager.getInstance().hide(); StreamStats.getInstance().onStoppedPlaying(); - MouseCursorHider.stop(); - TouchController.reset(); - GameBar.getInstance().disable(); + if (isFullVersion()) { + MouseCursorHider.stop(); + TouchController.reset(); + GameBar.getInstance().disable(); + } } window.addEventListener(BxEvent.STREAM_STOPPED, unload); @@ -313,7 +319,7 @@ window.addEventListener('pagehide', e => { BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); }); -window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => { +isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => { Screenshot.takeScreenshot(); }); @@ -368,15 +374,13 @@ function waitForRootDialog() { function main() { - waitForRootDialog(); - // Monkey patches patchRtcPeerConnection(); patchRtcCodecs(); interceptHttpRequests(); patchVideoApi(); patchCanvasContext(); - AppInterface && patchPointerLockApi(); + isFullVersion() && AppInterface && patchPointerLockApi(); getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && patchAudioContext(); @@ -385,52 +389,57 @@ function main() { disableAdobeAudienceManager(); } - STATES.userAgent.capabilities.touch && TouchController.updateCustomList(); - overridePreloadState(); - - VibrationManager.initialSetup(); - - // Check for Update - BX_FLAGS.CheckForUpdate && checkForUpdate(); + waitForRootDialog(); // Setup UI addCss(); Toast.setup(); - (getPref(PrefKey.GAME_BAR_POSITION) !== 'off') && GameBar.getInstance(); - Screenshot.setup(); GuideMenu.addEventListeners(); StreamBadges.setupEvents(); StreamStats.setupEvents(); - EmulatedMkbHandler.setupEvents(); - Patcher.init(); + if (isFullVersion()) { + (getPref(PrefKey.GAME_BAR_POSITION) !== 'off') && GameBar.getInstance(); + Screenshot.setup(); - disablePwa(); + STATES.userAgent.capabilities.touch && TouchController.updateCustomList(); + overridePreloadState(); + + VibrationManager.initialSetup(); + + // Check for Update + BX_FLAGS.CheckForUpdate && checkForUpdate(); + + Patcher.init(); + disablePwa(); + + // Preload Remote Play + if (getPref(PrefKey.REMOTE_PLAY_ENABLED)) { + RemotePlayManager.detect(); + } + + if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.ALL) { + TouchController.setup(); + } + + // Start PointerProviderServer + if (getPref(PrefKey.MKB_ENABLED) && AppInterface) { + STATES.pointerServerPort = AppInterface.startPointerServer() || 9269; + BxLogger.info('startPointerServer', 'Port', STATES.pointerServerPort.toString()); + } + + // Show wait time in game card + getPref(PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME) && GameTile.setup(); + + EmulatedMkbHandler.setupEvents(); + } // Show a toast when connecting/disconecting controller if (getPref(PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS)) { window.addEventListener('gamepadconnected', e => showGamepadToast(e.gamepad)); window.addEventListener('gamepaddisconnected', e => showGamepadToast(e.gamepad)); } - - // Preload Remote Play - if (getPref(PrefKey.REMOTE_PLAY_ENABLED)) { - RemotePlayManager.detect(); - } - - if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.ALL) { - TouchController.setup(); - } - - // Start PointerProviderServer - if (getPref(PrefKey.MKB_ENABLED) && AppInterface) { - STATES.pointerServerPort = AppInterface.startPointerServer() || 9269; - BxLogger.info('startPointerServer', 'Port', STATES.pointerServerPort.toString()); - } - - // Show wait time in game card - getPref(PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME) && GameTile.setup(); } main(); diff --git a/src/macros/build.ts b/src/macros/build.ts index 286e761..1e7f102 100644 --- a/src/macros/build.ts +++ b/src/macros/build.ts @@ -1,5 +1,13 @@ import stylus from 'stylus'; +export const isFullVersion = () => { + return Bun.env.BUILD_VARIANT === 'full'; +}; + +export const isLiteVersion = () => { + return Bun.env.BUILD_VARIANT === 'lite'; +}; + export const renderStylus = async () => { const file = Bun.file('./src/assets/css/styles.styl'); const cssStr = await file.text(); diff --git a/src/modules/stream-player.ts b/src/modules/stream-player.ts index 17733b1..b57908d 100644 --- a/src/modules/stream-player.ts +++ b/src/modules/stream-player.ts @@ -1,3 +1,5 @@ +import { isFullVersion } from "@macros/build" with {type: "macro"}; + import { CE } from "@/utils/html"; import { WebGL2Player } from "./player/webgl2-player"; import { Screenshot } from "@/utils/screenshot"; @@ -232,7 +234,7 @@ export class StreamPlayer { webGL2Player.setFilter(2); } - Screenshot.updateCanvasFilters('none'); + isFullVersion() && Screenshot.updateCanvasFilters('none'); webGL2Player.setSharpness(options.sharpness || 0); webGL2Player.setSaturation(options.saturation || 100); @@ -246,7 +248,7 @@ export class StreamPlayer { } // Apply video filters to screenshots - if (getPref(PrefKey.SCREENSHOT_APPLY_FILTERS)) { + if (isFullVersion() && getPref(PrefKey.SCREENSHOT_APPLY_FILTERS)) { Screenshot.updateCanvasFilters(filters); } diff --git a/src/modules/stream/stream-badges.ts b/src/modules/stream/stream-badges.ts index c8b03cc..39ddd1c 100644 --- a/src/modules/stream/stream-badges.ts +++ b/src/modules/stream/stream-badges.ts @@ -1,9 +1,12 @@ +import { isLiteVersion } from "@macros/build" with {type: "macro"}; + import { t } from "@utils/translation"; import { BxEvent } from "@utils/bx-event"; import { CE, createSvgIcon } from "@utils/html"; import { STATES } from "@utils/global"; import { BxLogger } from "@/utils/bx-logger"; import { BxIcon } from "@/utils/bx-icon"; +import { GuideMenuTab } from "../ui/guide-menu"; enum StreamBadge { PLAYTIME = 'playtime', @@ -344,24 +347,20 @@ export class StreamBadges { } catch(e) {} }); - /* - Don't do this until xCloud remove the Stream Menu page + // Since the Lite version doesn't have the "..." button on System menu + // we need to display Stream badges in the Guide menu instead + isLiteVersion() && window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, async e => { + const where = (e as any).where as GuideMenuTab; - window.addEventListener(BxEvent.XCLOUD_GUIDE_SHOWN, async e => { - const where = (e as any).where as XcloudGuideWhere; - - if (where !== XcloudGuideWhere.HOME || !STATES.isPlaying) { + if (where !== GuideMenuTab.HOME || !STATES.isPlaying) { return; } const $btnQuit = document.querySelector('#gamepass-dialog-root a[class*=QuitGameButton]'); - if (!$btnQuit) { - return; + if ($btnQuit) { + // Add badges + $btnQuit.insertAdjacentElement('beforebegin', await StreamBadges.getInstance().render()); } - - // Add badges - $btnQuit.insertAdjacentElement('beforebegin', await StreamBadges.getInstance().render()); }); - */ } } diff --git a/src/modules/ui/dialog/settings-dialog.ts b/src/modules/ui/dialog/settings-dialog.ts index 6157787..d771ce2 100644 --- a/src/modules/ui/dialog/settings-dialog.ts +++ b/src/modules/ui/dialog/settings-dialog.ts @@ -1,3 +1,5 @@ +import { isFullVersion } from "@macros/build" with {type: "macro"}; + import { onChangeVideoPlayerType, updateVideoPlayer } from "@/modules/stream/stream-settings-utils"; import { ButtonStyle, CE, createButton, createSvgIcon, removeChildElements, type BxButton } from "@/utils/html"; import { NavigationDialog, NavigationDirection } from "./navigation-dialog"; @@ -10,7 +12,7 @@ import { TouchController } from "@/modules/touch-controller"; import { VibrationManager } from "@/modules/vibration-manager"; import { BxEvent } from "@/utils/bx-event"; import { BxIcon } from "@/utils/bx-icon"; -import { STATES, AppInterface, deepClone, SCRIPT_VERSION, STORAGE } from "@/utils/global"; +import { STATES, AppInterface, deepClone, SCRIPT_VERSION, STORAGE, SCRIPT_VARIANT } from "@/utils/global"; import { t, Translations } from "@/utils/translation"; import { BxSelectElement } from "@/web-components/bx-select"; import { setNearby } from "@/utils/navigation-utils"; @@ -38,6 +40,7 @@ type SettingTabContentItem = Partial<{ onChange: (e: any, value: number) => void; onCreated: (setting: SettingTabContentItem, $control: any) => void; params: any; + requiredVariants?: BuildVariant | Array; }> type SettingTabContent = { @@ -48,12 +51,14 @@ type SettingTabContent = { helpUrl?: string; content?: any; items?: Array void) | false>; + requiredVariants?: BuildVariant | Array; }; type SettingTab = { icon: SVGElement; group: 'global'; items: Array; + requiredVariants?: BuildVariant | Array; }; export class SettingsNavigationDialog extends NavigationDialog { @@ -205,12 +210,14 @@ export class SettingsNavigationDialog extends NavigationDialog { PrefKey.STREAM_COMBINE_SOURCES, ], }, { + requiredVariants: 'full', group: 'co-op', label: t('local-co-op'), items: [ PrefKey.LOCAL_CO_OP_ENABLED, ], }, { + requiredVariants: 'full', group: 'mkb', label: t('mouse-and-keyboard'), items: [ @@ -219,6 +226,7 @@ export class SettingsNavigationDialog extends NavigationDialog { PrefKey.MKB_HIDE_IDLE_CURSOR, ], }, { + requiredVariants: 'full', group: 'touch-control', label: t('touch-controller'), note: !STATES.userAgent.capabilities.touch ? '⚠️ ' + t('device-unsupported-touch') : null, @@ -247,6 +255,7 @@ export class SettingsNavigationDialog extends NavigationDialog { PrefKey.UI_HIDE_SECTIONS, ], }, { + requiredVariants: 'full', group: 'game-bar', label: t('game-bar'), items: [ @@ -357,6 +366,7 @@ export class SettingsNavigationDialog extends NavigationDialog { }]; private readonly TAB_DISPLAY_ITEMS: Array = [{ + requiredVariants: 'full', group: 'audio', label: t('audio'), helpUrl: 'https://better-xcloud.github.io/ingame-features/#audio', @@ -503,14 +513,15 @@ export class SettingsNavigationDialog extends NavigationDialog { }]; private readonly TAB_NATIVE_MKB_ITEMS: Array = [{ + requiredVariants: 'full', group: 'native-mkb', label: t('native-mkb'), - items: [{ + items: [isFullVersion() && { pref: PrefKey.NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY, onChange: (e: any, value: number) => { NativeMkbHandler.getInstance().setVerticalScrollMultiplier(value / 100); }, - }, { + }, isFullVersion() && { pref: PrefKey.NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY, onChange: (e: any, value: number) => { NativeMkbHandler.getInstance().setHorizontalScrollMultiplier(value / 100); @@ -519,9 +530,10 @@ export class SettingsNavigationDialog extends NavigationDialog { }]; private readonly TAB_SHORTCUTS_ITEMS: Array = [{ + requiredVariants: 'full', group: 'controller-shortcuts', label: t('controller-shortcuts'), - content: ControllerShortcut.renderSettings(), + content: isFullVersion() && ControllerShortcut.renderSettings(), }]; private readonly TAB_STATS_ITEMS: Array = [{ @@ -575,24 +587,28 @@ export class SettingsNavigationDialog extends NavigationDialog { icon: BxIcon.CONTROLLER, group: 'controller', items: this.TAB_CONTROLLER_ITEMS, + requiredVariants: 'full', }, getPref(PrefKey.MKB_ENABLED) && { icon: BxIcon.VIRTUAL_CONTROLLER, group: 'mkb', items: this.TAB_VIRTUAL_CONTROLLER_ITEMS, + requiredVariants: 'full', }, AppInterface && getPref(PrefKey.NATIVE_MKB_ENABLED) === 'on' && { icon: BxIcon.NATIVE_MKB, group: 'native-mkb', items: this.TAB_NATIVE_MKB_ITEMS, + requiredVariants: 'full', }, { icon: BxIcon.COMMAND, group: 'shortcuts', items: this.TAB_SHORTCUTS_ITEMS, + requiredVariants: 'full', }, { @@ -715,6 +731,15 @@ export class SettingsNavigationDialog extends NavigationDialog { } } + private isSupportedVariant(requiredVariants: BuildVariant | Array | undefined) { + if (typeof requiredVariants === 'undefined') { + return true; + } + + requiredVariants = typeof requiredVariants === 'string' ? [requiredVariants] : requiredVariants; + return requiredVariants.includes(SCRIPT_VARIANT); + } + private async renderSuggestions(e: Event) { const $btnSuggest = (e.target as HTMLElement).closest('div')!; $btnSuggest.toggleAttribute('bx-open'); @@ -1101,6 +1126,10 @@ export class SettingsNavigationDialog extends NavigationDialog { prefDefinition = getPrefDefinition(pref); } + if (prefDefinition && !this.isSupportedVariant(prefDefinition.requiredVariants)) { + return; + } + let label = prefDefinition?.label || setting.label; let note = prefDefinition?.note || setting.note; const experimental = prefDefinition?.experimental || setting.experimental; @@ -1237,6 +1266,11 @@ export class SettingsNavigationDialog extends NavigationDialog { continue; } + // Don't render unsupported build variant + if (!this.isSupportedVariant(settingTab.requiredVariants)) { + continue; + } + // Don't render other tabs in unsupported regions if (settingTab.group !== 'global' && !this.renderFullSettings) { continue; @@ -1255,6 +1289,10 @@ export class SettingsNavigationDialog extends NavigationDialog { continue; } + if (!this.isSupportedVariant(settingTabContent.requiredVariants)) { + continue; + } + // Don't render other settings in unsupported regions if (!this.renderFullSettings && settingTab.group === 'global' && settingTabContent.group !== 'general' && settingTabContent.group !== 'footer') { continue; @@ -1265,6 +1303,11 @@ export class SettingsNavigationDialog extends NavigationDialog { // If label is "Better xCloud" => create a link to Releases page if (label === t('better-xcloud')) { label += ' ' + SCRIPT_VERSION; + + if (SCRIPT_VARIANT === 'lite') { + label += ' (Lite)'; + } + label = createButton({ label: label, url: 'https://github.com/redphx/better-xcloud/releases', diff --git a/src/modules/ui/guide-menu.ts b/src/modules/ui/guide-menu.ts index 6cec33f..9d337a7 100644 --- a/src/modules/ui/guide-menu.ts +++ b/src/modules/ui/guide-menu.ts @@ -1,3 +1,5 @@ +import { isFullVersion } from "@macros/build" with {type: "macro"}; + import { BxEvent } from "@/utils/bx-event"; import { AppInterface, STATES } from "@/utils/global"; import { createButton, ButtonStyle, CE } from "@/utils/html"; @@ -22,7 +24,7 @@ export class GuideMenu { }, {once: true}); // Close all xCloud's dialogs - window.BX_EXPOSED.dialogRoutes.closeAll(); + GuideMenu.#closeGuideMenu(); }, }), @@ -53,7 +55,7 @@ export class GuideMenu { } // Close all xCloud's dialogs - window.BX_EXPOSED.dialogRoutes.closeAll(); + GuideMenu.#closeGuideMenu(); }, }), @@ -66,7 +68,7 @@ export class GuideMenu { confirm(t('back-to-home-confirm')) && (window.location.href = window.location.href.substring(0, 31)); // Close all xCloud's dialogs - window.BX_EXPOSED.dialogRoutes.closeAll(); + GuideMenu.#closeGuideMenu(); }, attributes: { 'data-state': 'playing', @@ -76,6 +78,17 @@ export class GuideMenu { static #$renderedButtons: HTMLElement; + static #closeGuideMenu() { + if (window.BX_EXPOSED.dialogRoutes) { + window.BX_EXPOSED.dialogRoutes.closeAll(); + return; + } + + // Use alternative method for Lite version + const $btnClose = document.querySelector('#gamepass-dialog-root button[class^=Header-module__closeButton]') as HTMLElement; + $btnClose && $btnClose.click(); + } + static #renderButtons() { if (GuideMenu.#$renderedButtons) { return GuideMenu.#$renderedButtons; @@ -115,9 +128,11 @@ export class GuideMenu { } static #injectHome($root: HTMLElement, isPlaying = false) { - const $achievementsProgress = $root.querySelector('button[class*=AchievementsButton-module__progressBarContainer]'); - if ($achievementsProgress) { - TrueAchievements.injectAchievementsProgress($achievementsProgress as HTMLElement); + if (isFullVersion()) { + const $achievementsProgress = $root.querySelector('button[class*=AchievementsButton-module__progressBarContainer]'); + if ($achievementsProgress) { + TrueAchievements.injectAchievementsProgress($achievementsProgress as HTMLElement); + } } // Find the element to add buttons to @@ -162,7 +177,7 @@ export class GuideMenu { static observe($addedElm: HTMLElement) { const className = $addedElm.className; - if (className.includes('AchievementsButton-module__progressBarContainer')) { + if (isFullVersion() && className.includes('AchievementsButton-module__progressBarContainer')) { TrueAchievements.injectAchievementsProgress($addedElm); return; } @@ -174,10 +189,12 @@ export class GuideMenu { } // Achievement Details page - const $achievDetailPage = $addedElm.querySelector('div[class*=AchievementDetailPage]'); - if ($achievDetailPage) { - TrueAchievements.injectAchievementDetailPage($achievDetailPage as HTMLElement); - return; + if (isFullVersion()) { + const $achievDetailPage = $addedElm.querySelector('div[class*=AchievementDetailPage]'); + if ($achievDetailPage) { + TrueAchievements.injectAchievementDetailPage($achievDetailPage as HTMLElement); + return; + } } // Find navigation bar diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 0df97ec..478f83a 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,7 +1,9 @@ +type BuildVariant = 'full' | 'lite'; + // Get type of an array's element type ArrayElement = ArrayType extends readonly (infer ElementType)[] ? ElementType : never; -type PartialRecord = Partial> +type PartialRecord = Partial>; interface Window { AppInterface: any; diff --git a/src/types/setting-definition.d.ts b/src/types/setting-definition.d.ts index 044c995..c6cf57c 100644 --- a/src/types/setting-definition.d.ts +++ b/src/types/setting-definition.d.ts @@ -24,6 +24,7 @@ export type SettingDefinition = { suggest: PartialRecord, ready: (setting: SettingDefinition) => void; type: SettingElementType, + requiredVariants: BuildVariant | Array; // migrate?: (this: Preferences, savedPrefs: any, value: any) => void; }> & ( {} | { diff --git a/src/utils/bx-exposed.ts b/src/utils/bx-exposed.ts index a55a3f8..9dc8f00 100644 --- a/src/utils/bx-exposed.ts +++ b/src/utils/bx-exposed.ts @@ -1,3 +1,5 @@ +import { isFullVersion } from "@macros/build" with {type: "macro"}; + import { ControllerShortcut } from "@/modules/controller-shortcut"; import { BxEvent } from "@utils/bx-event"; import { deepClone, STATES } from "@utils/global"; @@ -110,8 +112,8 @@ export const BxExposed = { } }, - handleControllerShortcut: ControllerShortcut.handle, - resetControllerShortcut: ControllerShortcut.reset, + handleControllerShortcut: isFullVersion() && ControllerShortcut.handle, + resetControllerShortcut: isFullVersion() && ControllerShortcut.reset, overrideSettings: { 'Tv_settings': { diff --git a/src/utils/global.ts b/src/utils/global.ts index 6b96216..254305f 100644 --- a/src/utils/global.ts +++ b/src/utils/global.ts @@ -2,6 +2,7 @@ import type { BaseSettingsStore } from "./settings-storages/base-settings-storag import { UserAgent } from "./user-agent"; export const SCRIPT_VERSION = Bun.env.SCRIPT_VERSION!; +export const SCRIPT_VARIANT = Bun.env.BUILD_VARIANT! as BuildVariant; export const AppInterface = window.AppInterface; diff --git a/src/utils/screenshot.ts b/src/utils/screenshot.ts index b68e5fb..32fb1f8 100644 --- a/src/utils/screenshot.ts +++ b/src/utils/screenshot.ts @@ -31,7 +31,7 @@ export class Screenshot { } static updateCanvasFilters(filters: string) { - Screenshot.#canvasContext.filter = filters; + Screenshot.#canvasContext && (Screenshot.#canvasContext.filter = filters); } static #onAnimationEnd(e: Event) { diff --git a/src/utils/settings-storages/base-settings-storage.ts b/src/utils/settings-storages/base-settings-storage.ts index 957278d..249960d 100644 --- a/src/utils/settings-storages/base-settings-storage.ts +++ b/src/utils/settings-storages/base-settings-storage.ts @@ -3,6 +3,7 @@ import type { NumberStepperParams, SettingDefinitions } from "@/types/setting-de import { BxEvent } from "../bx-event"; import { SettingElementType } from "../setting-element"; import { t } from "../translation"; +import { SCRIPT_VARIANT } from "../global"; export class BaseSettingsStore { private storage: Storage; @@ -18,6 +19,11 @@ export class BaseSettingsStore { for (settingId in definitions) { const setting = definitions[settingId]; + // Convert requiredVariants to array + if (typeof setting.requiredVariants === 'string') { + setting.requiredVariants = [setting.requiredVariants]; + } + /* if (setting.migrate && settingId in savedPrefs) { setting.migrate.call(this, savedPrefs, savedPrefs[settingId]); @@ -58,9 +64,16 @@ export class BaseSettingsStore { return; } + const definition = this.definitions[key]; + + // Return default value if build variant is different + if (definition.requiredVariants && !definition.requiredVariants.includes(SCRIPT_VARIANT)) { + return definition.default; + } + // Return default value if the feature is not supported - if (checkUnsupported && this.definitions[key].unsupported) { - return this.definitions[key].default; + if (checkUnsupported && definition.unsupported) { + return definition.default; } if (!(key in this.settings)) { diff --git a/src/utils/settings-storages/global-settings-storage.ts b/src/utils/settings-storages/global-settings-storage.ts index 95425ad..1f8297b 100644 --- a/src/utils/settings-storages/global-settings-storage.ts +++ b/src/utils/settings-storages/global-settings-storage.ts @@ -197,6 +197,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.SCREENSHOT_APPLY_FILTERS]: { + requiredVariants: 'full', label: t('screenshot-apply-filters'), default: false, }, @@ -211,6 +212,8 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.STREAM_COMBINE_SOURCES]: { + requiredVariants: 'full', + label: t('combine-audio-video-streams'), default: false, experimental: true, @@ -218,6 +221,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.STREAM_TOUCH_CONTROLLER]: { + requiredVariants: 'full', label: t('tc-availability'), default: StreamTouchController.ALL, options: { @@ -233,11 +237,13 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, }, [PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF]: { + requiredVariants: 'full', label: t('tc-auto-off'), default: false, unsupported: !STATES.userAgent.capabilities.touch, }, [PrefKey.STREAM_TOUCH_CONTROLLER_DEFAULT_OPACITY]: { + requiredVariants: 'full', type: SettingElementType.NUMBER_STEPPER, label: t('tc-default-opacity'), default: 100, @@ -252,6 +258,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { unsupported: !STATES.userAgent.capabilities.touch, }, [PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD]: { + requiredVariants: 'full', label: t('tc-standard-layout-style'), default: 'default', options: { @@ -262,6 +269,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { unsupported: !STATES.userAgent.capabilities.touch, }, [PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM]: { + requiredVariants: 'full', label: t('tc-custom-layout-style'), default: 'default', options: { @@ -276,15 +284,18 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { default: false, }, [PrefKey.MKB_HIDE_IDLE_CURSOR]: { + requiredVariants: 'full', label: t('hide-idle-cursor'), default: false, }, [PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG]: { + requiredVariants: 'full', label: t('disable-post-stream-feedback-dialog'), default: false, }, [PrefKey.BITRATE_VIDEO_MAX]: { + requiredVariants: 'full', type: SettingElementType.NUMBER_STEPPER, label: t('bitrate-video-maximum'), note: '⚠️ ' + t('unexpected-behavior'), @@ -306,10 +317,11 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, suggest: { highest: 0, - } + }, }, [PrefKey.GAME_BAR_POSITION]: { + requiredVariants: 'full', label: t('position'), default: 'bottom-left', options: { @@ -320,6 +332,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.LOCAL_CO_OP_ENABLED]: { + requiredVariants: 'full', label: t('enable-local-co-op-support'), default: false, note: CE('a', { @@ -341,15 +354,18 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.CONTROLLER_ENABLE_SHORTCUTS]: { + requiredVariants: 'full', default: false, }, [PrefKey.CONTROLLER_ENABLE_VIBRATION]: { + requiredVariants: 'full', label: t('controller-vibration'), default: true, }, [PrefKey.CONTROLLER_DEVICE_VIBRATION]: { + requiredVariants: 'full', label: t('device-vibration'), default: ControllerDeviceVibration.OFF, options: { @@ -360,6 +376,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.CONTROLLER_VIBRATION_INTENSITY]: { + requiredVariants: 'full', label: t('vibration-intensity'), type: SettingElementType.NUMBER_STEPPER, default: 100, @@ -373,6 +390,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.MKB_ENABLED]: { + requiredVariants: 'full', label: t('enable-mkb'), default: false, unsupported: ((): string | boolean => { @@ -398,6 +416,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.NATIVE_MKB_ENABLED]: { + requiredVariants: 'full', label: t('native-mkb'), default: 'default', options: { @@ -419,6 +438,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY]: { + requiredVariants: 'full', label: t('horizontal-scroll-sensitivity'), type: SettingElementType.NUMBER_STEPPER, default: 0, @@ -438,6 +458,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY]: { + requiredVariants: 'full', label: t('vertical-scroll-sensitivity'), type: SettingElementType.NUMBER_STEPPER, default: 0, @@ -457,10 +478,12 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.MKB_DEFAULT_PRESET_ID]: { + requiredVariants: 'full', default: 0, }, [PrefKey.MKB_ABSOLUTE_MOUSE]: { + requiredVariants: 'full', default: false, }, @@ -470,6 +493,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.UI_LOADING_SCREEN_GAME_ART]: { + requiredVariants: 'full', label: t('show-game-art'), default: true, }, @@ -493,6 +517,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.UI_LAYOUT]: { + requiredVariants: 'full', label: t('layout'), default: 'default', options: { @@ -508,11 +533,13 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.UI_HOME_CONTEXT_MENU_DISABLED]: { + requiredVariants: 'full', label: t('disable-home-context-menu'), default: STATES.browser.capabilities.touch, }, [PrefKey.UI_HIDE_SECTIONS]: { + requiredVariants: 'full', label: t('hide-sections'), default: [], multipleOptions: { @@ -529,6 +556,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME]: { + requiredVariants: 'full', label: t('show-wait-time-in-game-card'), default: false, }, @@ -663,6 +691,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { default: false, }, [PrefKey.AUDIO_ENABLE_VOLUME_CONTROL]: { + requiredVariants: 'full', label: t('enable-volume-control'), default: false, }, @@ -743,11 +772,13 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.REMOTE_PLAY_ENABLED]: { + requiredVariants: 'full', label: t('enable-remote-play-feature'), default: false, }, [PrefKey.REMOTE_PLAY_RESOLUTION]: { + requiredVariants: 'full', default: StreamResolution.DIM_1080P, options: { [StreamResolution.DIM_1080P]: '1080p', @@ -756,6 +787,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { }, [PrefKey.GAME_FORTNITE_FORCE_CONSOLE]: { + requiredVariants: 'full', label: '🎮 ' + t('fortnite-force-console-version'), default: false, note: t('fortnite-allow-stw-mode'), diff --git a/src/utils/true-achievements.ts b/src/utils/true-achievements.ts index e17ebd1..6c10d94 100644 --- a/src/utils/true-achievements.ts +++ b/src/utils/true-achievements.ts @@ -1,5 +1,5 @@ import { BxIcon } from "./bx-icon"; -import { AppInterface, STATES } from "./global"; +import { AppInterface, SCRIPT_VARIANT, STATES } from "./global"; import { ButtonStyle, CE, clearDataSet, createButton, getReactProps } from "./html"; import { t } from "./translation"; @@ -27,7 +27,7 @@ export class TrueAchievements { TrueAchievements.open(true, dataset.xboxTitleId, dataset.id); // Close all xCloud's dialogs - window.BX_EXPOSED.dialogRoutes.closeAll(); + window.BX_EXPOSED.dialogRoutes?.closeAll(); } private static $hiddenLink = CE('a', { @@ -53,6 +53,11 @@ export class TrueAchievements { } static injectAchievementsProgress($elm: HTMLElement) { + // Only do this in Full version + if (SCRIPT_VARIANT !== 'full') { + return; + } + const $parent = $elm.parentElement!; // Wrap xCloud's element with our own @@ -89,6 +94,11 @@ export class TrueAchievements { } static injectAchievementDetailPage($parent: HTMLElement) { + // Only do this in Full version + if (SCRIPT_VARIANT !== 'full') { + return; + } + const props = getReactProps($parent); if (!props) { return;