@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 3.0.4
// @version 3.1.1
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@ -13,11 +13,12 @@
// ==/UserScript==
'use strict' ;
const SCRIPT _VERSION = '3.0.4 ' ;
const SCRIPT _VERSION = '3.1.1 ' ;
const SCRIPT _HOME = 'https://github.com/redphx/better-xcloud' ;
const ENABLE _XCLOUD _LOGGER = false ;
const ENABLE _PRELOAD _BX _UI = false ;
const USE _DEV _TOUCH _LAYOUT = false ;
const ENABLE _NATIVE _MKB _BETA = false ;
window . NATIVE _MKB _TITLES = [
@ -46,6 +47,8 @@ const BxEvent = {
STREAM _STARTING : 'bx-stream-starting' ,
STREAM _STARTED : 'bx-stream-started' ,
STREAM _STOPPED : 'bx-stream-stopped' ,
CUSTOM _TOUCH _LAYOUTS _LOADED : 'bx-custom-touch-layouts-loaded' ,
} ;
// Quickly create a tree of elements without having to use innerHTML
@ -706,6 +709,7 @@ const Translations = {
"en-US" : "Deadzone counterweight" ,
"es-ES" : "Contrapeso de la zona muerta" ,
"ja-JP" : "デッドゾーンのカウンターウエイト" ,
"pl-PL" : "Przeciwwaga martwej strefy" ,
"pt-BR" : "Contador da Zona Morta" ,
"ru-RU" : "Противодействие мертвой зоне игры" ,
"tr-TR" : "Ölü alan denge ağı rlı ğı " ,
@ -1280,6 +1284,17 @@ const Translations = {
"vi-VN" : "Nhấn vào để kích hoạt" ,
"zh-CN" : "单击以启用" ,
} ,
"mkb-disclaimer" : {
"de-DE" : "Das Nutzen dieser Funktion beim Online-Spielen könnte als Betrug angesehen werden" ,
"en-US" : "Using this feature when playing online could be viewed as cheating" ,
"es-ES" : "Usar esta función al jugar en línea podría ser visto como trampas" ,
"ja-JP" : "オンラインプレイでこの機能を使用すると不正行為と判定される可能性があります" ,
"pl-PL" : "Używanie tej funkcji podczas grania online może być postrzegane jako oszukiwanie" ,
"pt-BR" : "Usar esta função em jogos online pode ser considerado como uma forma de trapaça" ,
"ru-RU" : "Использование этой функции при игре онлайн может рассматриваться как читерство" ,
"uk-UA" : "Використання цієї функції під час гри онлайн може розглядатися як шахрайство" ,
"vi-VN" : "Sử dụng chức năng này khi chơi trực tuyến có thể bị xem là gian lận" ,
} ,
"mouse-and-keyboard" : {
"de-DE" : "Maus & Tastatur" ,
"en-US" : "Mouse & Keyboard" ,
@ -2161,6 +2176,7 @@ const Translations = {
"en-US" : "Stick decay minimum" ,
"es-ES" : "Disminuir mínimamente el analógico" ,
"ja-JP" : "スティックの減衰の最小値" ,
"pl-PL" : "Minimalne opóźnienie drążka" ,
"pt-BR" : "Mínimo decaimento do analógico" ,
"ru-RU" : "Минимальная перезарядка стика" ,
"tr-TR" : "Çubuğun ortalanma süresi minimumu" ,
@ -2173,6 +2189,7 @@ const Translations = {
"en-US" : "Stick decay strength" ,
"es-ES" : "Intensidad de decaimiento del analógico" ,
"ja-JP" : "スティックの減衰の強さ" ,
"pl-PL" : "Siła opóźnienia drążka" ,
"pt-BR" : "Força de decaimento do analógico" ,
"ru-RU" : "Скорость перезарядки стика" ,
"tr-TR" : "Çubuğun ortalanma gücü" ,
@ -2217,6 +2234,7 @@ const Translations = {
"en-US" : "Support Better xCloud" ,
"es-ES" : "Apoyar a Better xCloud" ,
"ja-JP" : "Better xCloudをサポート" ,
"pl-PL" : "Wesprzyj Better xCloud" ,
"pt-BR" : "Suporte ao Melhor xCloud" ,
"ru-RU" : "Поддержать Better xCloud" ,
"tr-TR" : "Better xCloud'a destek ver" ,
@ -2744,6 +2762,7 @@ window.addEventListener('load', e => {
} ) ;
const NATIVE _FETCH = window . fetch ;
const SERVER _REGIONS = { } ;
var IS _PLAYING = false ;
var STREAM _WEBRTC ;
@ -2752,9 +2771,12 @@ var STREAM_AUDIO_GAIN_NODE;
var $STREAM _VIDEO ;
var $SCREENSHOT _CANVAS ;
var GAME _TITLE _ID ;
var GAME _XBOX _TITLE _ID ;
var GAME _PRODUCT _ID ;
var APP _CONTEXT ;
window . BX _EXPOSED = { } ;
let IS _REMOTE _PLAYING ;
let REMOTE _PLAY _CONFIG ;
@ -2776,6 +2798,8 @@ const Icon = {
REMOTE _PLAY : '<g transform="matrix(.492308 0 0 .581818 -14.7692 -11.6364)"><clipPath id="A"><path d="M30 20h65v55H30z"/></clipPath><g clip-path="url(#A)"><g transform="matrix(.395211 0 0 .334409 11.913 7.01124)"><g transform="matrix(.555556 0 0 .555556 57.8889 -20.2417)" fill="none" stroke="#fff" stroke-width="13.88"><path d="M200 140.564c-42.045-33.285-101.955-33.285-144 0M168 165c-23.783-17.3-56.217-17.3-80 0"/></g><g transform="matrix(-.555556 0 0 -.555556 200.111 262.393)"><g transform="matrix(1 0 0 1 0 11.5642)"><path d="M200 129c-17.342-13.728-37.723-21.795-58.636-24.198C111.574 101.378 80.703 109.444 56 129" fill="none" stroke="#fff" stroke-width="13.88"/></g><path d="M168 165c-23.783-17.3-56.217-17.3-80 0" fill="none" stroke="#fff" stroke-width="13.88"/></g><g transform="matrix(.75 0 0 .75 32 32)"><path d="M24 72h208v93.881H24z" fill="none" stroke="#fff" stroke-linejoin="miter" stroke-width="9.485"/><circle cx="188" cy="128" r="12" stroke-width="10" transform="matrix(.708333 0 0 .708333 71.8333 12.8333)"/><path d="M24.358 103.5h110" fill="none" stroke="#fff" stroke-linecap="butt" stroke-width="10.282"/></g></g></g></g>' ,
HAND _TAP : '<path d="M6.537 8.906c0-4.216 3.469-7.685 7.685-7.685s7.685 3.469 7.685 7.685M7.719 30.778l-4.333-7.389C3.133 22.944 3 22.44 3 21.928a2.97 2.97 0 0 1 2.956-2.956 2.96 2.96 0 0 1 2.55 1.461l2.761 4.433V8.906a2.97 2.97 0 0 1 2.956-2.956 2.97 2.97 0 0 1 2.956 2.956v8.276a2.97 2.97 0 0 1 2.956-2.956 2.97 2.97 0 0 1 2.956 2.956v2.365a2.97 2.97 0 0 1 2.956-2.956A2.97 2.97 0 0 1 29 19.547v5.32c0 3.547-1.182 5.911-1.182 5.911"/>' ,
SCREENSHOT _B64 : 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDMyIDMyIiBmaWxsPSIjZmZmIj48cGF0aCBkPSJNMjguMzA4IDUuMDM4aC00LjI2NWwtMi4wOTctMy4xNDVhMS4yMyAxLjIzIDAgMCAwLTEuMDIzLS41NDhoLTkuODQ2YTEuMjMgMS4yMyAwIDAgMC0xLjAyMy41NDhMNy45NTYgNS4wMzhIMy42OTJBMy43MSAzLjcxIDAgMCAwIDAgOC43MzF2MTcuMjMxYTMuNzEgMy43MSAwIDAgMCAzLjY5MiAzLjY5MmgyNC42MTVBMy43MSAzLjcxIDAgMCAwIDMyIDI1Ljk2MlY4LjczMWEzLjcxIDMuNzEgMCAwIDAtMy42OTItMy42OTJ6bS02Ljc2OSAxMS42OTJjMCAzLjAzOS0yLjUgNS41MzgtNS41MzggNS41MzhzLTUuNTM4LTIuNS01LjUzOC01LjUzOCAyLjUtNS41MzggNS41MzgtNS41MzggNS41MzggMi41IDUuNTM4IDUuNTM4eiIvPjwvc3ZnPgo=' ,
} ;
@ -3103,6 +3127,7 @@ class TitlesInfo {
const details = titleInfo . details ;
TitlesInfo . update ( details . productId , {
titleId : titleInfo . titleId ,
xboxTitleId : details . xboxTitleId ,
// Has more than one input type -> must have touch support
hasTouchSupport : ( details . supportedInputTypes . length > 1 ) ,
} ) ;
@ -3319,7 +3344,7 @@ class LoadingScreen {
class TouchController {
static get # EVENT _SHOW _CONTROLLER ( ) {
static get # EVENT _SHOW _DEFAULT _ CONTROLLER ( ) {
return new MessageEvent ( 'message' , {
data : '{"content":"{\\"layoutId\\":\\"\\"}","target":"/streaming/touchcontrols/showlayoutv2","type":"Message"}' ,
origin : 'better-xcloud' ,
@ -3340,6 +3365,9 @@ class TouchController {
static # showing = false ;
static # dataChannel ;
static # customLayouts = { } ;
static # currentLayoutId ;
static enable ( ) {
TouchController . # enable = true ;
}
@ -3352,8 +3380,13 @@ class TouchController {
return TouchController . # enable ;
}
static # showDefault ( ) {
TouchController . # dispatchMessage ( TouchController . # EVENT _SHOW _DEFAULT _CONTROLLER ) ;
TouchController . # showing = true ;
}
static # show ( ) {
TouchController . # dispatchMessage ( TouchController . # EVENT _SHOW _CONTROLLER ) ;
TouchController . loadCustomLayout ( GAME _XBOX _TITLE _ID , TouchController . # currentLayoutId , 0 ) ;
TouchController . # showing = true ;
}
@ -3389,6 +3422,86 @@ class TouchController {
} , 10 ) ;
}
static getCustomLayouts ( xboxTitleId ) {
const dispatchLayouts = data => {
const event = new Event ( BxEvent . CUSTOM _TOUCH _LAYOUTS _LOADED ) ;
event . data = data ;
window . dispatchEvent ( event ) ;
} ;
xboxTitleId = '' + xboxTitleId ;
if ( xboxTitleId in TouchController . # customLayouts ) {
dispatchLayouts ( TouchController . # customLayouts [ xboxTitleId ] ) ;
return ;
}
let url = 'https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts/' ;
if ( USE _DEV _TOUCH _LAYOUT ) {
url += ` dev/ ${ xboxTitleId } .json ` ;
} else {
url += ` ${ xboxTitleId } .json ` ;
}
NATIVE _FETCH ( url )
. then ( resp => resp . json ( ) , ( ) => {
TouchController . # customLayouts [ xboxTitleId ] = null ;
// Wait for BX_EXPOSED.touch_layout_manager
setTimeout ( ( ) => dispatchLayouts ( null ) , 1000 ) ;
} )
. then ( json => {
// Normalize data
const schema _version = json . schema _version || 1 ;
let layout ;
try {
if ( schema _version === 1 ) {
json . layouts = {
default : {
name : 'Default' ,
content : json . layout ,
} ,
} ;
json . default _layout = 'default' ;
delete json . layout ;
}
} catch ( e ) { }
TouchController . # customLayouts [ xboxTitleId ] = json ;
// Wait for BX_EXPOSED.touch_layout_manager
setTimeout ( ( ) => dispatchLayouts ( json ) , 1000 ) ;
} ) ;
}
static loadCustomLayout ( xboxTitleId , layoutId , delay ) {
if ( ! window . BX _EXPOSED . touch _layout _manager ) {
return ;
}
TouchController . # currentLayoutId = layoutId ;
xboxTitleId = '' + xboxTitleId ;
const layoutData = TouchController . # customLayouts [ xboxTitleId ] ;
if ( ! xboxTitleId || ! layoutId || ! layoutData ) {
TouchController . # enable && TouchController . # showDefault ( ) ;
return ;
}
const layout = ( layoutData . layouts [ layoutId ] || layoutData . layouts [ layoutData . default _layout ] ) ;
layout && setTimeout ( ( ) => {
window . BX _EXPOSED . touch _layout _manager . changeLayoutForScope ( {
type : 'showLayout' ,
scope : xboxTitleId ,
subscope : 'base' ,
layout : {
id : 'System.Standard' ,
displayName : 'System' ,
layoutFile : {
content : layout . content ,
} ,
}
} ) ;
} , delay ) ;
}
static setup ( ) {
const $style = document . createElement ( 'style' ) ;
document . documentElement . appendChild ( $style ) ;
@ -3421,7 +3534,7 @@ class TouchController {
const nativeCreateDataChannel = RTCPeerConnection . prototype . createDataChannel ;
RTCPeerConnection . prototype . createDataChannel = function ( ) {
const dataChannel = nativeCreateDataChannel . apply ( this , arguments ) ;
if ( ! TouchController . # enable || dataChannel . label !== 'message' ) {
if ( dataChannel . label !== 'message' ) {
return dataChannel ;
}
@ -3455,8 +3568,18 @@ class TouchController {
// Dispatch a message to display generic touch controller
if ( msg . data . includes ( 'touchcontrols/showtitledefault' ) ) {
TouchController . # show ( ) ;
TouchController . # enable && TouchController . getCustomLayouts ( GAME _XBOX _TITLE _ID ) ;
return ;
}
// Load custom touch layout
try {
if ( msg . data . includes ( '/titleinfo' ) ) {
const json = JSON . parse ( JSON . parse ( msg . data ) . content ) ;
const xboxTitleId = parseInt ( json . titleid , 16 ) ;
GAME _XBOX _TITLE _ID = xboxTitleId ;
}
} catch ( e ) { console . log ( e ) }
} ) ;
return dataChannel ;
@ -6209,7 +6332,8 @@ class Preferences {
'ready' : ( ) => {
const options = Preferences . SETTINGS [ Preferences . STREAM _CODEC _PROFILE ] . options ;
if ( Object . keys ( options ) . length <= 1 ) {
Preferences . SETTINGS [ Preferences . STREAM _CODEC _PROFILE ] . unsupported = _ _ ( 'browser-unsupported-feature' ) ;
Preferences . SETTINGS [ Preferences . STREAM _CODEC _PROFILE ] . unsupported = true ;
Preferences . SETTINGS [ Preferences . STREAM _CODEC _PROFILE ] . note = '⚠️ ' + _ _ ( 'browser-unsupported-feature' ) ;
}
} ,
} ,
@ -6237,7 +6361,7 @@ class Preferences {
'all' : _ _ ( 'tc-all-games' ) ,
'off' : _ _ ( 'off' ) ,
} ,
'unsupported' : ! HAS _TOUCH _SUPPORT ? _ _ ( 'device-unsupported-touch' ) : false ,
'unsupported' : ! HAS _TOUCH _SUPPORT ,
} ,
[ Preferences . STREAM _TOUCH _CONTROLLER _STYLE _STANDARD ] : {
'default' : 'default' ,
@ -6246,7 +6370,7 @@ class Preferences {
'white' : _ _ ( 'tc-all-white' ) ,
'muted' : _ _ ( 'tc-muted-colors' ) ,
} ,
'unsupported' : ! HAS _TOUCH _SUPPORT ? _ _ ( 'device-unsupported-touch' ) : false ,
'unsupported' : ! HAS _TOUCH _SUPPORT ,
} ,
[ Preferences . STREAM _TOUCH _CONTROLLER _STYLE _CUSTOM ] : {
'default' : 'default' ,
@ -6254,7 +6378,7 @@ class Preferences {
'default' : _ _ ( 'default' ) ,
'muted' : _ _ ( 'tc-muted-colors' ) ,
} ,
'unsupported' : ! HAS _TOUCH _SUPPORT ? _ _ ( 'device-unsupported-touch' ) : false ,
'unsupported' : ! HAS _TOUCH _SUPPORT ,
} ,
[ Preferences . STREAM _SIMPLIFY _MENU ] : {
'default' : false ,
@ -6301,6 +6425,11 @@ class Preferences {
const userAgent = ( window . navigator . orgUserAgent || window . navigator . userAgent || '' ) . toLowerCase ( ) ;
return userAgent . match ( /(android|iphone|ipad)/ ) ? _ _ ( 'browser-unsupported-feature' ) : false ;
} ) ( ) ,
'ready' : ( ) => {
const pref = Preferences . SETTINGS [ Preferences . MKB _ENABLED ] ;
const note = _ _ ( pref . unsupported ? 'browser-unsupported-feature' : 'mkb-disclaimer' ) ;
Preferences . SETTINGS [ Preferences . MKB _ENABLED ] . note = '⚠️ ' + note ;
} ,
} ,
[ Preferences . MKB _DEFAULT _PRESET _ID ] : {
@ -6707,7 +6836,7 @@ class Patcher {
funcStr = funcStr . replace ( 'onServerDisconnectMessage(e){' , ` onServerDisconnectMessage(e) {
const msg = JSON.parse(e);
if (msg.reason === 'WarningForBeingIdle') {
if (msg.reason === 'WarningForBeingIdle' && !window.location.pathname.includes('/launch/') ) {
try {
this.sendKeepAlive();
return;
@ -6808,7 +6937,7 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
] ;
// Enable native Mouse and Keyboard support
if ( getPref ( Preferences . MKB _ENABLED ) ) {
if ( ENABLE _NATIVE _ MKB_BETA ) {
newSettings . push ( 'EnableMouseAndKeyboard: true' ) ;
newSettings . push ( 'ShowMouseKeyboardSetting: true' ) ;
@ -6888,6 +7017,16 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
funcStr = funcStr . substring ( 0 , bracketIndex ) + 'return 0;' + funcStr . substring ( bracketIndex ) ;
return funcStr ;
} ,
exposeTouchLayoutManager : function ( funcStr ) {
const text = 'this._perScopeLayoutsStream=new' ;
if ( ! funcStr . includes ( text ) ) {
return false ;
}
funcStr = funcStr . replace ( text , 'window.BX_EXPOSED["touch_layout_manager"] = this,' + text ) ;
return funcStr ;
} ,
} ;
static # PATCH _ORDERS = [
@ -6900,17 +7039,25 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
getPref ( Preferences . UI _LAYOUT ) === 'tv' && [ 'tvLayout' ] ,
ENABLE _XCLOUD _LOGGER && [ 'enableXcloudLogger' ] ,
ENABLE _XCLOUD _LOGGER && [
'enableXcloudLogger' ,
'enableConsoleLogging' ,
] ,
getPref ( Preferences . BLOCK _TRACKING ) && [
'disableTrackEvent' ,
'blockWebRtcStatsCollector' ,
] ,
getPref ( Preferences . REMOTE _PLAY _ENABLED ) && [
'remotePlayDirectConnectUrl' ,
'remotePlayKeepAlive' ,
] ,
[
'overrideSettings' ,
getPref ( Preferences . REMOTE _PLAY _ENABLED ) && 'remotePlayDirectConnectUrl ',
getPref ( Preferences . BLOCK _TRACKING ) && 'disableTrackEvent' ,
ENABLE _NATIVE _MKB _BETA && 'mkbIsMouseAndKeyboardTitle ',
HAS _TOUCH _SUPPORT && 'patchUpdateInputConfigurationAsync' ,
ENABLE _NATIVE _MKB _BETA && getPref ( Preferences . MKB _ENABLED ) && 'mkbIsMouseAndKeyboardTitle' ,
ENABLE _XCLOUD _LOGGER && 'enableConsoleLogging' ,
getPref ( Preferences . REMOTE _PLAY _ENABLED ) && 'remotePlayKeepAlive' ,
getPref ( Preferences . BLOCK _TRACKING ) && 'blockWebRtcStatsCollector' ,
] ,
] ;
@ -6919,12 +7066,13 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
getPref ( Preferences . REMOTE _PLAY _ENABLED ) && [ 'remotePlayConnectMode' ] ,
[ 'playVibration' ] ,
getPref ( Preferences . STREAM _TOUCH _CONTROLLER ) === 'all' && [ 'exposeTouchLayoutManager' ] ,
ENABLE _XCLOUD _LOGGER && [ 'enableConsoleLogging' ] ,
[
'disableGamepadDisconnectedScreen' ,
ENABLE _NATIVE _MKB _BETA && getPref ( Preferences . MKB _ENABLED ) && 'mkbMouseAndKeyboardEnabled' ,
ENABLE _NATIVE _MKB _BETA && 'mkbMouseAndKeyboardEnabled' ,
] ,
] ;
@ -7027,6 +7175,10 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
static # cleanupPatches ( ) {
for ( let groupIndex = Patcher . # PATCH _ORDERS . length - 1 ; groupIndex >= 0 ; groupIndex -- ) {
const group = Patcher . # PATCH _ORDERS [ groupIndex ] ;
if ( group === false ) {
Patcher . # PATCH _ORDERS . splice ( groupIndex , 1 ) ;
continue ;
}
for ( let patchIndex = group . length - 1 ; patchIndex >= 0 ; patchIndex -- ) {
const patchName = group [ patchIndex ] ;
@ -7830,11 +7982,11 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
overflow: hidden;
}
.bx-quick-settings-tab-contents div :not([data-clarity-boost="true"]) .bx-clarity-boost-warning {
.bx-quick-settings-bar :not([data-clarity-boost="true"]) .bx-clarity-boost-warning {
display: none;
}
.bx-quick-settings-tab-contents div [data-clarity-boost="true"] .bx-clarity-boost-warning {
.bx-quick-settings-bar [data-clarity-boost="true"] .bx-clarity-boost-warning {
display: block;
margin: 0px 8px;
padding: 12px;
@ -7844,7 +7996,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
border-radius: 4px;
}
.bx-quick-settings-tab-contents div [data-clarity-boost="true"] > div[data-type="video"] {
.bx-quick-settings-bar [data-clarity-boost="true"] div[data-type="video"] {
display: none;
}
@ -8591,8 +8743,6 @@ function interceptHttpRequests() {
const PREF _STREAM _TOUCH _CONTROLLER = getPref ( Preferences . STREAM _TOUCH _CONTROLLER ) ;
const PREF _AUDIO _MIC _ON _PLAYING = getPref ( Preferences . AUDIO _MIC _ON _PLAYING ) ;
const orgFetch = window . fetch ;
const consoleAddrs = { } ;
const patchIceCandidates = function ( ... arg ) {
@ -8601,7 +8751,7 @@ function interceptHttpRequests() {
const url = ( typeof request === 'string' ) ? request : request . url ;
if ( url && url . endsWith ( '/ice' ) && url . includes ( '/sessions/' ) && request . method === 'GET' ) {
const promise = orgFetch ( ... arg ) ;
const promise = NATIVE _FETCH ( ... arg ) ;
return promise . then ( response => {
return response . clone ( ) . text ( ) . then ( text => {
@ -8640,7 +8790,7 @@ function interceptHttpRequests() {
}
if ( IS _REMOTE _PLAYING && ( url . includes ( '/sessions/home' ) || url . includes ( 'inputconfigs' ) ) ) {
TouchController . en able( ) ;
TouchController . dis able( ) ;
const clone = request . clone ( ) ;
@ -8676,7 +8826,7 @@ function interceptHttpRequests() {
// Get console IP
if ( url . includes ( '/configuration' ) ) {
const promise = orgFetch ( ... arg ) ;
const promise = NATIVE _FETCH ( ... arg ) ;
return promise . then ( response => {
return response . clone ( ) . json ( ) . then ( obj => {
@ -8694,12 +8844,37 @@ function interceptHttpRequests() {
response . json = ( ) => Promise . resolve ( obj ) ;
response . text = ( ) => Promise . resolve ( JSON . stringify ( obj ) ) ;
return response ;
} ) ;
} ) ;
} else if ( PREF _STREAM _TOUCH _CONTROLLER === 'all' && url . includes ( 'inputconfigs' ) ) {
const promise = NATIVE _FETCH ( ... arg ) ;
return promise . then ( response => {
return response . clone ( ) . json ( ) . then ( obj => {
if ( obj [ 0 ] . supportedTabs . length > 0 ) {
TouchController . disable ( ) ;
const event = new Event ( BxEvent . CUSTOM _TOUCH _LAYOUTS _LOADED ) ;
event . data = null ;
window . dispatchEvent ( event ) ;
} else {
TouchController . enable ( ) ;
const xboxTitleId = JSON . parse ( opts . body ) . titleIds [ 0 ] ;
GAME _XBOX _TITLE _ID = xboxTitleId ;
TouchController . getCustomLayouts ( xboxTitleId ) ;
}
response . json = ( ) => Promise . resolve ( obj ) ;
response . text = ( ) => Promise . resolve ( JSON . stringify ( obj ) ) ;
return response ;
} ) ;
} ) ;
}
return patchIceCandidates ( ... arg ) || orgFetch ( ... arg ) ;
return patchIceCandidates ( ... arg ) || NATIVE _FETCH ( ... arg ) ;
}
if ( IS _REMOTE _PLAYING && url . includes ( '/login/user' ) ) {
@ -8723,7 +8898,7 @@ function interceptHttpRequests() {
console . log ( e ) ;
}
return orgFetch ( ... arg ) ;
return NATIVE _FETCH ( ... arg ) ;
}
if ( IS _REMOTE _PLAYING && url . includes ( '/titles' ) ) {
@ -8743,7 +8918,7 @@ function interceptHttpRequests() {
} ) ;
arg [ 0 ] = request ;
return orgFetch ( ... arg ) ;
return NATIVE _FETCH ( ... arg ) ;
}
// ICE server candidates
@ -8754,7 +8929,7 @@ function interceptHttpRequests() {
// Server list
if ( ! url . includes ( 'xhome.' ) && url . endsWith ( '/v2/login/user' ) ) {
const promise = orgFetch ( ... arg ) ;
const promise = NATIVE _FETCH ( ... arg ) ;
return promise . then ( response => {
return response . clone ( ) . json ( ) . then ( obj => {
@ -8829,12 +9004,12 @@ function interceptHttpRequests() {
} ) ;
arg [ 0 ] = newRequest ;
return orgFetch ( ... arg ) ;
return NATIVE _FETCH ( ... arg ) ;
}
// Get wait time
if ( PREF _UI _LOADING _SCREEN _WAIT _TIME && url . includes ( 'xboxlive.com' ) && url . includes ( '/waittime/' ) ) {
const promise = orgFetch ( ... arg ) ;
const promise = NATIVE _FETCH ( ... arg ) ;
return promise . then ( response => {
return response . clone ( ) . json ( ) . then ( json => {
if ( json . estimatedAllocationTimeInSeconds > 0 ) {
@ -8850,7 +9025,7 @@ function interceptHttpRequests() {
if ( url . endsWith ( '/configuration' ) && url . includes ( '/sessions/cloud/' ) && request . method === 'GET' ) {
PREF _UI _LOADING _SCREEN _GAME _ART && LoadingScreen . hide ( ) ;
const promise = orgFetch ( ... arg ) ;
const promise = NATIVE _FETCH ( ... arg ) ;
// Touch controller for all games
if ( PREF _STREAM _TOUCH _CONTROLLER === 'all' ) {
@ -8878,7 +9053,7 @@ function interceptHttpRequests() {
overrides . inputConfiguration = overrides . inputConfiguration || { } ;
overrides . inputConfiguration . enableVibration = true ;
if ( ENABLE _NATIVE _MKB _BETA ) {
overrides . inputConfiguration . enableMouseAndKeyboard = getPref ( Preferences . MKB _ENABLED ) ;
overrides . inputConfiguration . enableMouseAndKeyboard = true ;
}
// Enable touch controller
@ -8905,7 +9080,7 @@ function interceptHttpRequests() {
// catalog.gamepass
if ( url . startsWith ( 'https://catalog.gamepass.com' ) && url . includes ( '/products' ) ) {
const promise = orgFetch ( ... arg ) ;
const promise = NATIVE _FETCH ( ... arg ) ;
return promise . then ( response => {
return response . clone ( ) . json ( ) . then ( json => {
for ( let productId in json . Products ) {
@ -8917,8 +9092,8 @@ function interceptHttpRequests() {
} ) ;
}
if ( PREF _STREAM _TOUCH _CONTROLLER === 'all' && ( url . endsWith ( '/titles' ) || url . endsWith ( '/mru' ) ) ) {
const promise = orgFetch ( ... arg ) ;
if ( PREF _STREAM _TOUCH _CONTROLLER === 'all' && ( url . includes ( '/titles' ) || url . includes ( '/mru' ) ) ) {
const promise = NATIVE _FETCH ( ... arg ) ;
return promise . then ( response => {
return response . clone ( ) . json ( ) . then ( json => {
for ( let game of json . results ) {
@ -8941,7 +9116,7 @@ function interceptHttpRequests() {
} ) ;
}
return orgFetch ( ... arg ) ;
return NATIVE _FETCH ( ... arg ) ;
}
}
@ -9057,6 +9232,7 @@ function injectSettingsButton($parent) {
},
*/
[ _ _ ( 'touch-controller' ) ] : {
_note : ! HAS _TOUCH _SUPPORT ? '⚠️ ' + _ _ ( 'device-unsupported-touch' ) : null ,
[ Preferences . STREAM _TOUCH _CONTROLLER ] : _ _ ( 'tc-availability' ) ,
[ Preferences . STREAM _TOUCH _CONTROLLER _STYLE _STANDARD ] : _ _ ( 'tc-standard-layout-style' ) ,
[ Preferences . STREAM _TOUCH _CONTROLLER _STYLE _CUSTOM ] : _ _ ( 'tc-custom-layout-style' ) ,
@ -9102,14 +9278,8 @@ function injectSettingsButton($parent) {
const setting = Preferences . SETTINGS [ settingId ] ;
le t settingLabel ;
let settingN ote ;
if ( Array . isArray ( SETTINGS _UI [ groupLabel ] [ settingId ] ) ) {
[ settingLabel , settingNote ] = SETTINGS _UI [ groupLabel ] [ settingId ] ;
} else {
settingLabel = SETTINGS _UI [ groupLabel ] [ settingId ] ;
}
cons t settingLabel = SETTINGS _UI [ groupLabel ] [ settingId ] ;
const settingNote = setting. n ote ;
let $control , $inpCustomUserAgent ;
let labelAttrs = { } ;
@ -9180,11 +9350,8 @@ function injectSettingsButton($parent) {
// Disable unsupported settings
if ( setting . unsupported ) {
$control . disabled = true ;
$control . title = setting . unsupported ;
}
$control . disabled && ( $control . style . cursor = 'help' ) ;
const $label = CE ( 'label' , labelAttrs , settingLabel ) ;
if ( settingNote ) {
$label . appendChild ( CE ( 'b' , { } , settingNote ) ) ;
@ -9384,8 +9551,10 @@ function cloneStreamHudButton($orgButton, label, svg_icon) {
}
} ;
$container . addEventListener ( 'transitionstart' , onTransitionStart ) ;
$container . addEventListener ( 'transitionend ' , onTransitionEnd ) ;
if ( HAS _TOUCH _SUPPORT ) {
$container . addEventListener ( 'transitionstart ' , onTransitionStart ) ;
$container . addEventListener ( 'transitionend' , onTransitionEnd ) ;
}
const $button = $container . querySelector ( 'button' ) ;
$button . setAttribute ( 'title' , label ) ;
@ -9719,8 +9888,9 @@ function setupQuickSettingsBar() {
group : 'audio' ,
label : _ _ ( 'audio' ) ,
help _url : 'https://better-xcloud.github.io/ingame-features/#audio' ,
items : {
[ Preferences . AUDIO _VOLUME ] : {
items : [
{
pref : Preferences . AUDIO _VOLUME ,
label : _ _ ( 'volume' ) ,
onChange : ( e , value ) => {
STREAM _AUDIO _GAIN _NODE && ( STREAM _AUDIO _GAIN _NODE . gain . value = ( value / 100 ) . toFixed ( 2 ) ) ;
@ -9729,7 +9899,7 @@ function setupQuickSettingsBar() {
disabled : ! getPref ( Preferences . AUDIO _ENABLE _VOLUME _CONTROL ) ,
} ,
} ,
} ,
] ,
} ,
{
@ -9737,33 +9907,38 @@ function setupQuickSettingsBar() {
label : _ _ ( 'video' ) ,
help _url : 'https://better-xcloud.github.io/ingame-features/#video' ,
note : CE ( 'div' , { 'class' : 'bx-quick-settings-bar-note bx-clarity-boost-warning' } , ` ⚠️ ${ _ _ ( 'clarity-boost-warning' ) } ` ) ,
items : {
[ Preferences . VIDEO _RATIO ] : {
items : [
{
pref : Preferences . VIDEO _RATIO ,
label : _ _ ( 'ratio' ) ,
onChange : updateVideoPlayerCss ,
} ,
[ Preferences . VIDEO _CLARITY ] : {
{
pref : Preferences . VIDEO _CLARITY ,
label : _ _ ( 'clarity' ) ,
onChange : updateVideoPlayerCss ,
unsupported : isSafari ,
} ,
[ Preferences . VIDEO _SATURATION ] : {
{
pref : Preferences . VIDEO _SATURATION ,
label : _ _ ( 'saturation' ) ,
onChange : updateVideoPlayerCss ,
} ,
[ Preferences . VIDEO _CONTRAST ] : {
{
pref : Preferences . VIDEO _CONTRAST ,
label : _ _ ( 'contrast' ) ,
onChange : updateVideoPlayerCss ,
} ,
[ Preferences . VIDEO _BRIGHTNESS ] : {
{
pref : Preferences . VIDEO _BRIGHTNESS ,
label : _ _ ( 'brightness' ) ,
onChange : updateVideoPlayerCss ,
} ,
} ,
] ,
} ,
] ,
} ,
@ -9776,29 +9951,91 @@ function setupQuickSettingsBar() {
group : 'controller' ,
label : _ _ ( 'controller' ) ,
help _url : 'https://better-xcloud.github.io/ingame-features/#controller' ,
items : {
[ Preferences . CONTROLLER _ENABLE _VIBRATION ] : {
items : [
{
pref : Preferences . CONTROLLER _ENABLE _VIBRATION ,
label : _ _ ( 'controller-vibration' ) ,
unsupported : ! VibrationManager . supportControllerVibration ( ) ,
onChange : VibrationManager . updateGlobalVars ,
} ,
[ Preferences . CONTROLLER _DEVICE _VIBRATION ] : {
{
pref : Preferences . CONTROLLER _DEVICE _VIBRATION ,
label : _ _ ( 'device-vibration' ) ,
unsupported : ! VibrationManager . supportDeviceVibration ( ) ,
onChange : VibrationManager . updateGlobalVars ,
} ,
[ Preferences . CONTROLLER _VIBRATION _INTENSITY ] : (VibrationManager . supportControllerVibration ( ) || VibrationManager . supportDeviceVibration ( ) ) && {
( VibrationManager . supportControllerVibration ( ) || VibrationManager . supportDeviceVibration ( ) ) && {
pref : Preferences . CONTROLLER _VIBRATION _INTENSITY ,
label : _ _ ( 'vibration-intensity' ) ,
unsupported : ! VibrationManager . supportDeviceVibration ( ) ,
onChange : VibrationManager . updateGlobalVars ,
} ,
} ,
] ,
} ,
] ,
} ,
HAS _TOUCH _SUPPORT && {
icon : Icon . HAND _TAP ,
group : 'touch-controller' ,
items : [
{
group : 'touch-controller' ,
label : _ _ ( 'touch-controller' ) ,
items : [
{
label : _ _ ( 'layout' ) ,
content : CE ( 'select' , { disabled : true } , CE ( 'option' , { } , _ _ ( 'default' ) ) ) ,
onMounted : $elm => {
$elm . addEventListener ( 'change' , e => {
TouchController . loadCustomLayout ( GAME _XBOX _TITLE _ID , $elm . value , 1000 ) ;
} ) ;
window . addEventListener ( BxEvent . CUSTOM _TOUCH _LAYOUTS _LOADED , e => {
const data = e . data ;
if ( GAME _XBOX _TITLE _ID && $elm . xboxTitleId === GAME _XBOX _TITLE _ID ) {
$elm . dispatchEvent ( new Event ( 'change' ) ) ;
return ;
}
$elm . xboxTitleId = GAME _XBOX _TITLE _ID ;
// Clear options
while ( $elm . firstChild ) {
$elm . removeChild ( $elm . firstChild ) ;
}
$elm . disabled = ! data ;
if ( ! data ) {
$elm . appendChild ( CE ( 'option' , { value : '' } , _ _ ( 'default' ) ) ) ;
$elm . value = '' ;
$elm . dispatchEvent ( new Event ( 'change' ) ) ;
return ;
}
// Add options
const $fragment = document . createDocumentFragment ( ) ;
for ( const key in data . layouts ) {
const layout = data . layouts [ key ] ;
const $option = CE ( 'option' , { value : key } , layout . name ) ;
$fragment . appendChild ( $option ) ;
}
$elm . appendChild ( $fragment ) ;
$elm . value = data . default _layout ;
$elm . dispatchEvent ( new Event ( 'change' ) ) ;
} ) ;
} ,
} ,
] ,
}
] ,
} ,
{
icon : Icon . STREAM _STATS ,
group : 'stats' ,
@ -9807,41 +10044,49 @@ function setupQuickSettingsBar() {
group : 'stats' ,
label : _ _ ( 'menu-stream-stats' ) ,
help _url : 'https://better-xcloud.github.io/stream-stats/' ,
items : {
[ Preferences . STATS _SHOW _WHEN _PLAYING ] : {
items : [
{
pref : Preferences . STATS _SHOW _WHEN _PLAYING ,
label : _ _ ( 'show-stats-on-startup' ) ,
} ,
[ Preferences . STATS _QUICK _GLANCE ] : {
{
pref : Preferences . STATS _QUICK _GLANCE ,
label : _ _ ( 'enable-quick-glance-mode' ) ,
onChange : e => {
e . target . checked ? StreamStats . quickGlanceSetup ( ) : StreamStats . quickGlanceStop ( ) ;
} ,
} ,
[ Preferences . STATS _ITEMS ] : {
{
pref : Preferences . STATS _ITEMS ,
label : _ _ ( 'stats' ) ,
onChange : StreamStats . refreshStyles ,
} ,
[ Preferences . STATS _POSITION ] : {
{
pref : Preferences . STATS _POSITION ,
label : _ _ ( 'position' ) ,
onChange : StreamStats . refreshStyles ,
} ,
[ Preferences . STATS _TEXT _SIZE ] : {
{
pref : Preferences . STATS _TEXT _SIZE ,
label : _ _ ( 'text-size' ) ,
onChange : StreamStats . refreshStyles ,
} ,
[ Preferences . STATS _OPACITY ] : {
{
pref : Preferences . STATS _OPACITY ,
label : _ _ ( 'opacity' ) ,
onChange : StreamStats . refreshStyles ,
} ,
[ Preferences . STATS _TRANSPARENT ] : {
{
pref : Preferences . STATS _TRANSPARENT ,
label : _ _ ( 'transparent-background' ) ,
onChange : StreamStats . refreshStyles ,
} ,
[ Preferences . STATS _CONDITIONAL _FORMATTING ] : {
{
pref : Preferences . STATS _CONDITIONAL _FORMATTING ,
label : _ _ ( 'conditional-formatting' ) ,
onChange : StreamStats . refreshStyles ,
} ,
} ,
] ,
} ,
] ,
} ,
@ -9916,14 +10161,21 @@ function setupQuickSettingsBar() {
continue ;
}
for ( const pref in settingGroup . items ) {
const setting = settingGroup . items [pref ] ;
i f ( ! settingGroup . items ) {
settingGroup . items = [ ] ;
}
for ( const setting of settingGroup . items ) {
if ( ! setting ) {
continue ;
}
const pref = setting . pref ;
let $control ;
if ( ! setting . unsupported ) {
if ( setting . content ) {
$control = setting . content ;
} else if ( ! setting . unsupported ) {
$control = PREFS . toElement ( pref , setting . onChange , setting . params ) ;
}
@ -9936,6 +10188,8 @@ function setupQuickSettingsBar() {
) ;
$group . appendChild ( $content ) ;
setting . onMounted && setting . onMounted ( $control ) ;
}
}
@ -10088,6 +10342,7 @@ function onStreamStarted($video) {
GAME _PRODUCT _ID = matches . groups . product _id ;
} else {
GAME _TITLE _ID = 'remote-play' ;
GAME _PRODUCT _ID = null ;
}
// Enable MKB