// ==UserScript== // @name Xbox CLoud Gaming优化整合 // @name:zh-CN Xbox CLoud Gaming优化整合 // @namespace http://tampermonkey.net/xbox/nft // @version 3.9.3.9 // @author 奈非天 // @license MIT // @match https://www.xbox.com/*/play* // @run-at document-start // @grant none // @require https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.4.1/jquery.min.js // @original-script https://greasyfork.org/zh-CN/scripts/455741-xbox-cloud-gaming%E4%BC%98%E5%8C%96%E6%95%B4%E5%90%88 // @description:zh-cn 脚本免费!谨防上当受骗!整合和修改现有脚本,优化项详见脚本说明。【若你有好的想法或者BUG可以进xbox云游戏QQ交流1群531602832,2群313340764,3群826510890反馈】 // @description 脚本免费!谨防上当受骗!整合和修改现有脚本,优化项详见脚本说明。【若你有好的想法或者BUG可以进xbox云游戏QQ交流1群531602832,2群313340764,3群826510890反馈】 // @downloadURL none // ==/UserScript== (function () { 'use strict'; // Your code here... //★★★★★★★★★★★★★★★★★★★★Reference Project License Agreement Begin 参考项目许可协议开始★★★★★★★★★★★★★★★★★★★★// //屏蔽触控,流监控,视频调整,分辨率切换,画质切换,ipv6,串流通过该开源项目源码修改整合 https://github.com/redphx/better-xcloud /* better-xcloud MIT License Copyright (c) 2023 redphx Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ //★★★★★★★★★★★★★★★★★★★★Reference Project License Agreement End 参考项目许可协议结束★★★★★★★★★★★★★★★★★★★★// let nftxboxversion = 'v3.9.3.9'; let naifeitian = { isType(obj) { return Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1').toLowerCase(); }, getValue(key) { try { return JSON.parse(localStorage.getItem(key)); } catch (e) { return localStorage.getItem(key); } }, setValue(key, value) { if (this.isType(value) === 'object' || this.isType(value) === 'array' || this.isType(value) === 'boolean') { return localStorage.setItem(key, JSON.stringify(value)); } return localStorage.setItem(key, value); }, isValidIP(ip) { let reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/ return reg.test(ip); }, isNumber(val) { return !isNaN(parseFloat(val)) && isFinite(val); }, toElement(key, onChange) { const CE = createElement; const setting = key; const currentValue = key['default'] == undefined ? key : key['default']; let $control; if (setting['options'] != undefined) { $control = CE('select', { id: 'xcloud_setting_' + key['name'] }); for (let value in setting.options) { const label = setting.options[value]; const $option = CE('option', { value: value }, label); $control.appendChild($option); } $control.value = currentValue; $control.addEventListener('change', e => { key['default'] = e.target.value; this.setValue(key['name'], key); onChange && onChange(e); }); } else if (typeof setting.default === 'number') { $control = CE('input', { 'type': 'number', 'min': setting.min, 'max': setting.max }); $control.value = currentValue; $control.addEventListener('change', e => { let value = Math.max(setting.min, Math.min(setting.max, parseInt(e.target.value))); e.target.value = value; key['default'] = e.target.value this.setValue(key['name'], key); onChange && onChange(e); }); } else { $control = CE('input', { 'type': 'checkbox' }); $control.checked = currentValue; $control.addEventListener('change', e => { key['default'] = e.target.checked; NFTconfig[key['name'].slice(0,-2)]['default']=e.target.checked; this.setValue(key['name'], key); if(key['name']=='STATS_SLIDE_OPENGM' && e.target.checked){ if(this.getValue('STATS_SHOW_WHEN_PLAYINGGM')['default']){ $('#xcloud_setting_STATS_SHOW_WHEN_PLAYINGGM').click(); } }else if(key['name']=='STATS_SHOW_WHEN_PLAYINGGM' && e.target.checked){ if(this.getValue('STATS_SLIDE_OPENGM')['default']){ $('#xcloud_setting_STATS_SLIDE_OPENGM').click(); } } onChange && onChange(e); }); } $control.id = `xcloud_setting_${key.name}`; return $control; }, isSafari() { let userAgent = userAgentOriginal.toLowerCase(); if (userAgent.indexOf('safari') !== -1 && userAgent.indexOf('chrome') === -1) { return true; } else { return false; } }, getGM(defaultValue, n) { let newval = this.getValue(n) == null ? defaultValue : this.getValue(n); if(newval?.options!=undefined){ newval.options=defaultValue.options; } naifeitian.setValue(n, newval); return newval; }, showSetting(){ $('#settingsBackgroud').css('display', ''); $('body').css('overflow','hidden'); }, hideSetting(){ $('#settingsBackgroud').css('display', 'none'); $('body').css('overflow','visible'); }, patchFunctionBind() { Function.prototype.nativeBind = Function.prototype.bind; Function.prototype.bind = function() { let valid = false; if (arguments.length === 2 && arguments[0] === null) { if (arguments[1] === 0 || (typeof arguments[1] === 'function')) { valid = true; } } if (!valid) { return this.nativeBind.apply(this, arguments); } if (typeof arguments[1] === 'function') { console.log('还原 Function.prototype.bind()'); Function.prototype.bind = Function.prototype.nativeBind; } const orgFunc = this; const newFunc = (a, item) => { if (NFTconfig['remotePatchName'].length === 0) { orgFunc(a, item); return; } naifeitian.patch(item); orgFunc(a, item); } return newFunc.nativeBind.apply(newFunc, arguments); }; }, patch(item) { for (let id in item[1]) { if (NFTconfig['remotePatchName'].length <= 0) { return; } const func = item[1][id]; const funcStr = func.toString(); //替换str for(let i=0;i { NFTconfig[key] = naifeitian.getGM(NFTconfig[key], key + 'GM'); }); NFTconfig['remotePatchName']=['remotePlayDirectConnectUrl','remotePlayKeepAlive','remotePlayConnectMode','EnableStreamGate']; const originFetch = fetch; let regionsMenuItemList = []; let languageMenuItemList = []; let crturl=""; let canShowOC=null; let letmeOb = true; let checkIpsuc=false; let STREAM_WEBRTC; const ICON_STREAM_STATS = ''; const ICON_VIDEO_SETTINGS = ''; // Quickly create a tree of elements without having to use innerHTML function createElement(elmName, props = {}) { let $elm; const hasNs = 'xmlns' in props; if (hasNs) { $elm = document.createElementNS(props.xmlns, elmName); } else { $elm = document.createElement(elmName); } for (let key in props) { if (key === 'xmlns') { continue; } if (!props.hasOwnProperty(key) || $elm.hasOwnProperty(key)) { continue; } if (hasNs) { $elm.setAttributeNS(null, key, props[key]); } else { $elm.setAttribute(key, props[key]); } } for (let i = 2, size = arguments.length; i < size; i++) { const arg = arguments[i]; const argType = typeof arg; if (argType === 'string' || argType === 'number') { $elm.textContent = arg; } else if (arg) { $elm.appendChild(arg); } } return $elm; } function setMachineFullScreen() { try { let element = document.documentElement; if (element.requestFullscreen) { element.requestFullscreen(); } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen(); } else if (element.msRequestFullscreen) { element.msRequestFullscreen(); } else if (element.webkitRequestFullscreen) { element.webkitRequestFullScreen(); } screen?.orientation?.lock("landscape"); } catch (e) { } } function exitMachineFullscreen() { try { screen?.orientation?.unlock(); if (document.exitFullScreen) { document.exitFullScreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (element.msExitFullscreen) { element.msExitFullscreen(); } } catch (e) { } } function exitGame() { canShowOC=null; setTimeout(RemotePlay.detect, 10); document.documentElement.style.overflowY = ""; letmeOb = true; transitionComplete = true; StreamStats.stop(); bindmslogoevent(); const $quickBar = document.querySelector('.better-xcloud-quick-settings-bar'); if ($quickBar) { $quickBar.style.display = 'none'; } if (NFTconfig['autoFullScreen'] == 1) { exitMachineFullscreen(); } if (NFTconfig['noPopSetting'] == 0) { $('#popSetting').css('display', 'block'); } } function inGame(){ if(!IS_REMOTE_PLAYING){ let path=window.location.pathname; history.pushState({}, null, window.location.pathname.substr(0,window.location.pathname.indexOf("/launch/"))); history.pushState({}, null, path); } document.documentElement.style.overflowY = "hidden"; document.body.style.top = '0px'; if (NFTconfig['autoFullScreen'] == 1) { setMachineFullScreen(); } if (NFTconfig['noPopSetting'] == 0) { $('#popSetting').css('display', 'none'); } } class StreamBadges { static get BADGE_PLAYTIME() { return '游玩时间'; }; static get BADGE_BATTERY() { return '电量'; }; static get BADGE_IN() { return '下载'; }; static get BADGE_OUT() { return '上传'; }; static get BADGE_SERVER() { return '服务器'; }; static get BADGE_VIDEO() { return '解码'; }; static get BADGE_AUDIO() { return '音频'; }; static get BADGE_BREAK() { return 'break'; }; static ipv6 = false; static resolution = null; static video = null; static audio = null; static fps = 0; static region = ''; static startBatteryLevel = 100; static startTimestamp = 0; static #cachedDoms = {}; static #interval; static get #REFRESH_INTERVAL() { return 3000; }; static #renderBadge(name, value, color) { const CE = createElement; if (name === StreamBadges.BADGE_BREAK) { return CE('div', {'style': 'display: block'}); } let $badge; if (StreamBadges.#cachedDoms[name]) { $badge = StreamBadges.#cachedDoms[name]; $badge.lastElementChild.textContent = value; return $badge; } $badge = CE('div', {'class': 'better-xcloud-badge'}, CE('span', {'class': 'better-xcloud-badge-name'}, name), CE('span', {'class': 'better-xcloud-badge-value', 'style': `background-color: ${color}`}, value)); if (name === StreamBadges.BADGE_BATTERY) { $badge.classList.add('better-xcloud-badge-battery'); } StreamBadges.#cachedDoms[name] = $badge; return $badge; } static async #updateBadges(forceUpdate) { if (!forceUpdate && !document.querySelector('.better-xcloud-badges')) { StreamBadges.#stop(); return; } // 游玩时间 let now = +new Date; const diffSeconds = Math.ceil((now - StreamBadges.startTimestamp) / 1000); const playtime = StreamBadges.#secondsToHm(diffSeconds); // 电量 let batteryLevel = '100%'; let batteryLevelInt = 100; let isCharging = false; if (navigator.getBattery) { try { const bm = await navigator.getBattery(); isCharging = bm.charging; batteryLevelInt = Math.round(bm.level * 100); batteryLevel = `${batteryLevelInt}%`; if (batteryLevelInt != StreamBadges.startBatteryLevel) { const diffLevel = Math.round(batteryLevelInt - StreamBadges.startBatteryLevel); const sign = diffLevel > 0 ? '+' : ''; batteryLevel += ` (${sign}${diffLevel}%)`; } } catch(e) {} } const stats = await STREAM_WEBRTC.getStats(); let totalIn = 0; let totalOut = 0; stats.forEach(stat => { if (stat.type === 'candidate-pair' && stat.state == 'succeeded') { totalIn += stat.bytesReceived; totalOut += stat.bytesSent; } }); const badges = { [StreamBadges.BADGE_IN]: totalIn ? StreamBadges.#humanFileSize(totalIn) : null, [StreamBadges.BADGE_OUT]: totalOut ? StreamBadges.#humanFileSize(totalOut) : null, [StreamBadges.BADGE_PLAYTIME]: playtime, [StreamBadges.BADGE_BATTERY]: batteryLevel, }; for (let name in badges) { const value = badges[name]; if (value === null) { continue; } const $elm = StreamBadges.#cachedDoms[name]; $elm && ($elm.lastElementChild.textContent = value); if (name === StreamBadges.BADGE_BATTERY) { // Show charging status $elm.setAttribute('data-charging', isCharging); if (StreamBadges.startBatteryLevel === 100 && batteryLevelInt === 100) { $elm.style.display = 'none'; } else { $elm.style = ''; } } } } static #stop() { StreamBadges.#interval && clearInterval(StreamBadges.#interval); StreamBadges.#interval = null; } static #secondsToHm(seconds) { const h = Math.floor(seconds / 3600); const m = Math.floor(seconds % 3600 / 60) + 1; const hDisplay = h > 0 ? `${h}小时`: ''; const mDisplay = m > 0 ? `${m}分钟`: ''; return hDisplay + mDisplay; } // https://stackoverflow.com/a/20732091 static #humanFileSize(size) { let i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; } static async render() { // Video let video = ''; if (StreamBadges.resolution) { video = `${StreamBadges.resolution.height}p`; } if (StreamBadges.video) { video && (video += '/'); video += StreamBadges.video.codec; if (StreamBadges.video.profile) { let profile = StreamBadges.video.profile; profile = profile.startsWith('4d') ? '高' : (profile.startsWith('42e') ? '中' : '低'); video += ` (${profile})`; } } // 音频 let audio; if (StreamBadges.audio) { audio = StreamBadges.audio.codec; const bitrate = StreamBadges.audio.bitrate / 1000; audio += ` (${bitrate} kHz)`; } // 电量 let batteryLevel = ''; if (navigator.getBattery) { batteryLevel = '100%'; } // Server + Region let server = StreamBadges.region; server += '@' + (StreamBadges.ipv6 ? 'IPv6' : 'IPv4'); const BADGES = [ [StreamBadges.BADGE_PLAYTIME, '1m', '#ff004d'], [StreamBadges.BADGE_BATTERY, batteryLevel, '#00b543'], [StreamBadges.BADGE_IN, StreamBadges.#humanFileSize(0), '#29adff'], [StreamBadges.BADGE_OUT, StreamBadges.#humanFileSize(0), '#ff77a8'], [StreamBadges.BADGE_BREAK], [StreamBadges.BADGE_SERVER, server, '#ff6c24'], video ? [StreamBadges.BADGE_VIDEO, video, '#742f29'] : null, audio ? [StreamBadges.BADGE_AUDIO, audio, '#5f574f'] : null, ]; const $wrapper = createElement('div', {'class': 'better-xcloud-badges'}); BADGES.forEach(item => item && $wrapper.appendChild(StreamBadges.#renderBadge(...item))); await StreamBadges.#updateBadges(true); StreamBadges.#stop(); StreamBadges.#interval = setInterval(StreamBadges.#updateBadges, StreamBadges.#REFRESH_INTERVAL); return $wrapper; } } class StreamStats { static #interval; static #updateInterval = 1000; static #$container; static #$fps; static #$rtt; static #$dt; static #$pl; static #$fl; static #$br; static #$settings; static #lastStat; static start() { clearInterval(StreamStats.#interval); StreamStats.#$container.classList.remove('better-xcloud-gone'); StreamStats.#interval = setInterval(StreamStats.update, StreamStats.#updateInterval); } static stop() { clearInterval(StreamStats.#interval); StreamStats.#$container.classList.add('better-xcloud-gone'); StreamStats.#interval = null; StreamStats.#lastStat = null; } static toggle() { StreamStats.#isHidden() ? StreamStats.start() : StreamStats.stop(); } static #isHidden = () => StreamStats.#$container.classList.contains('better-xcloud-gone'); static update() { if (StreamStats.#isHidden() || !STREAM_WEBRTC) { StreamStats.stop(); return; } try{ STREAM_WEBRTC.getStats().then(stats => { stats.forEach(stat => { let grade = ''; if (stat.type === 'inbound-rtp' && stat.kind === 'video') { // FPS StreamStats.#$fps.textContent = stat.framesPerSecond || 0; // Packets Lost const packetsLost = stat.packetsLost; if (packetsLost != undefined) { const packetsReceived = stat.packetsReceived; const packetsLostPercentage = (packetsLost * 100 / ((packetsLost + packetsReceived) || 1)).toFixed(2); StreamStats.#$pl.textContent = `${packetsLost} (${packetsLostPercentage}%)`; }else{ StreamStats.#$pl.textContent = `-1 (-1%)`; } // Frames Dropped const framesDropped = stat.framesDropped; if (framesDropped != undefined) { const framesReceived = stat.framesReceived; const framesDroppedPercentage = (framesDropped * 100 / ((framesDropped + framesReceived) || 1)).toFixed(2); StreamStats.#$fl.textContent = `${framesDropped} (${framesDroppedPercentage}%)`; }else{ StreamStats.#$fl.textContent = `-1 (-1%)`; } if (StreamStats.#lastStat) { const lastStat = StreamStats.#lastStat; // Bitrate const timeDiff = stat.timestamp - lastStat.timestamp; const bitrate = 8 * (stat.bytesReceived - lastStat.bytesReceived) / timeDiff / 1000; StreamStats.#$br.textContent = `${bitrate.toFixed(2)} Mbps`; // Decode time const totalDecodeTimeDiff = stat.totalDecodeTime - lastStat.totalDecodeTime; const framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded; const currentDecodeTime = totalDecodeTimeDiff / framesDecodedDiff * 1000; StreamStats.#$dt.textContent = `${currentDecodeTime.toFixed(2)}ms`; if (NFTconfig['STATS_CONDITIONAL_FORMATTING']['default']) { grade = (currentDecodeTime > 12) ? 'bad' : (currentDecodeTime > 9) ? 'ok' : (currentDecodeTime > 6) ? 'good' : ''; } StreamStats.#$dt.setAttribute('data-grade', grade); } StreamStats.#lastStat = stat; } else if (stat.type === 'candidate-pair' && stat.state === 'succeeded') { // Round Trip Time const roundTripTime = typeof stat.currentRoundTripTime !== 'undefined' ? stat.currentRoundTripTime * 1000 : '???'; StreamStats.#$rtt.textContent = `${roundTripTime}ms`; if (NFTconfig['STATS_CONDITIONAL_FORMATTING']['default']) { grade = (roundTripTime > 100) ? 'bad' : (roundTripTime > 75) ? 'ok' : (roundTripTime > 40) ? 'good' : ''; } StreamStats.#$rtt.setAttribute('data-grade', grade); } }); }); }catch(e) {} } static #refreshStyles() { const PREF_POSITION = NFTconfig['STATS_POSITION']['default']; const PREF_TRANSPARENT = NFTconfig['STATS_TRANSPARENT']['default']; const PREF_OPACITY = NFTconfig['STATS_OPACITY']['default']; const PREF_TEXT_SIZE = NFTconfig['STATS_TEXT_SIZE']['default']; StreamStats.#$container.setAttribute('data-position', PREF_POSITION); StreamStats.#$container.setAttribute('data-transparent', PREF_TRANSPARENT); StreamStats.#$container.style.opacity = PREF_OPACITY + '%'; StreamStats.#$container.style.fontSize = PREF_TEXT_SIZE; } static hideSettingsUi() { StreamStats.#$settings.style.display = 'none'; } static #toggleSettingsUi() { const display = StreamStats.#$settings.style.display; StreamStats.#$settings.style.display = display === 'block' ? 'none' : 'block'; } static render() { if (StreamStats.#$container) { return; } const CE = createElement; StreamStats.#$container = CE('div', { 'class': 'better-xcloud-stats-bar better-xcloud-gone' }, CE('label', {}, '帧率'), StreamStats.#$fps = CE('span', {}, 0), CE('label', {}, '延迟'), StreamStats.#$rtt = CE('span', {}, '0ms'), CE('label', {}, '解码'), StreamStats.#$dt = CE('span', {}, '0ms'), CE('label', {}, '码率'), StreamStats.#$br = CE('span', {}, '0 Mbps'), CE('label', {}, '丢包'), StreamStats.#$pl = CE('span', {}, '0 (0.00%)'), CE('label', {}, '丢帧'), StreamStats.#$fl = CE('span', {}, '0 (0.00%)')); let clickTimeout; StreamStats.#$container.addEventListener('mousedown', e => { clearTimeout(clickTimeout); if (clickTimeout) { // Double-clicked clickTimeout = null; StreamStats.#toggleSettingsUi(); return; } clickTimeout = setTimeout(() => { clickTimeout = null; }, 400); }); document.documentElement.appendChild(StreamStats.#$container); const refreshFunc = e => { StreamStats.#refreshStyles() }; const $position = naifeitian.toElement(NFTconfig['STATS_POSITION'], refreshFunc); let $close; const $showStartup = naifeitian.toElement(NFTconfig['STATS_SHOW_WHEN_PLAYING'], refreshFunc); const $transparent = naifeitian.toElement(NFTconfig['STATS_TRANSPARENT'], refreshFunc); const $formatting = naifeitian.toElement(NFTconfig['STATS_CONDITIONAL_FORMATTING'], refreshFunc); const $opacity = naifeitian.toElement(NFTconfig['STATS_OPACITY'], refreshFunc); const $textSize = naifeitian.toElement(NFTconfig['STATS_TEXT_SIZE'], refreshFunc); const $slideopen = naifeitian.toElement(NFTconfig['STATS_SLIDE_OPEN'], refreshFunc); StreamStats.#$settings = CE('div', { 'class': 'better-xcloud-stats-settings' }, CE('b', {}, '状态条设置'), CE('div', {}, CE('label', { 'for': `xcloud_setting_NFTconfig['STATS_SHOW_WHEN_PLAYING']` }, '游戏启动时显示状态条'), $showStartup ), CE('div', {}, CE('label', {}, '位置'), $position ), CE('div', {}, CE('label', {}, '字体大小'), $textSize ), CE('div', {}, CE('label', { 'for': `xcloud_setting_STATS_OPACITY` }, '透明度 (10-100%)'), $opacity ), CE('div', {}, CE('label', { 'for': `xcloud_setting_STATS_TRANSPARENT` }, '背景透明'), $transparent ), CE('div', {}, CE('label', { 'for': `xcloud_setting_STATS_CONDITIONAL_FORMATTING` }, '数值颜色'), $formatting ), CE('div', {}, CE('label', { 'for': `xcloud_setting_STATS_SLIDE_OPEN` }, '悬浮窗展开时打开'), $slideopen ), $close = CE('button', {}, '关闭')); $close.addEventListener('click', e => StreamStats.hideSettingsUi()); document.documentElement.appendChild(StreamStats.#$settings); StreamStats.#refreshStyles(); } } function numberPicker(key, suffix = '', disabled = false) { const setting = key.name; let value = key.default; let $text, $decBtn, $incBtn; const MIN = key.min; const MAX = key.max; const CE = createElement; const $wrapper = CE('div', {}, $decBtn = CE('button', { 'data-type': 'dec' }, '-'), $text = CE('span', {}, value + suffix), $incBtn = CE('button', { 'data-type': 'inc' }, '+'), ); if (disabled) { $incBtn.disabled = true; $incBtn.classList.add('better-xcloud-hidden'); $decBtn.disabled = true; $decBtn.classList.add('better-xcloud-hidden'); return $wrapper; } let interval; let isHolding = false; const onClick = e => { if (isHolding) { e.preventDefault(); isHolding = false; return; } const btnType = e.target.getAttribute('data-type'); if (btnType === 'dec') { value = (value <= MIN) ? MIN : value - 1; } else { value = (value >= MAX) ? MAX : value + 1; } $text.textContent = value + suffix; key['default'] = value; naifeitian.setValue(key['name'], key); updateVideoPlayerCss(); isHolding = false; } const onMouseDown = e => { isHolding = true; const args = arguments; interval = setInterval(() => { const event = new Event('click'); event.arguments = args; e.target.dispatchEvent(event); }, 200); }; const onMouseUp = e => { clearInterval(interval); isHolding = false; }; $decBtn.addEventListener('click', onClick); $decBtn.addEventListener('mousedown', onMouseDown); $decBtn.addEventListener('mouseup', onMouseUp); $decBtn.addEventListener('touchstart', onMouseDown); $decBtn.addEventListener('touchend', onMouseUp); $incBtn.addEventListener('click', onClick); $incBtn.addEventListener('mousedown', onMouseDown); $incBtn.addEventListener('mouseup', onMouseUp); $incBtn.addEventListener('touchstart', onMouseDown); $incBtn.addEventListener('touchend', onMouseUp); return $wrapper; } function setupVideoSettingsBar() { const CE = createElement; let $stretchInp; const refreshFunc = e => { updateVideoPlayerCss(); }; const $stretch = naifeitian.toElement(NFTconfig['video_stretch'], refreshFunc); const $wrapper = CE('div', { 'class': 'better-xcloud-quick-settings-bar' }, CE('div', {}, CE('label', { 'for': 'better-xcloud-quick-setting-stretch' }, '去黑边'), $stretch), CE('div', {}, CE('label', {}, '清晰'), numberPicker(NFTconfig['VIDEO_CLARITY'], '', naifeitian.isSafari())), CE('div', {}, CE('label', {}, '饱和'), numberPicker(NFTconfig['VIDEO_SATURATION'], '%')), CE('div', {}, CE('label', {}, '对比'), numberPicker(NFTconfig['VIDEO_CONTRAST'], '%')), CE('div', {}, CE('label', {}, '亮度'), numberPicker(NFTconfig['VIDEO_BRIGHTNESS'], '%')) ); $stretch.addEventListener('change', e => { if (e.target.value == 'setting') { $('#video_stretch_x_y').css('display', 'block'); } else { $('#video_stretch_x_y').css('display', 'none'); } NFTconfig['video_stretch'].default = e.target.value; naifeitian.setValue('video_stretchGM', NFTconfig['video_stretch']); updateVideoPlayerCss(); }); document.documentElement.appendChild($wrapper); if ($stretch.id == 'xcloud_setting_video_stretchGM') { let dom = $('#xcloud_setting_video_stretchGM'); dom.after(`
左右
上下
`); $(document).on('blur', '.video_stretch_x_y_Listener', function () { let newval = $(this).val(); if (naifeitian.isNumber($(this).val())) { if ($(this).attr('id') == 'video_stretch_x') { NFTconfig['video_stretch_x_y']['x'] = newval; naifeitian.setValue('video_stretch_x_yGM', NFTconfig['video_stretch_x_y']); } else { NFTconfig['video_stretch_x_y']['y'] = newval; naifeitian.setValue('video_stretch_x_yGM', NFTconfig['video_stretch_x_y']); } } else { $(this).val("0"); NFTconfig['video_stretch_x_y']['x'] = 0; NFTconfig['video_stretch_x_y']['y'] = 0; naifeitian.setValue('video_stretch_x_yGM', NFTconfig['video_stretch_x_y']); } updateVideoPlayerCss(); }); } } function cloneStreamMenuButton($orgButton, label, svg_icon) { const $button = $orgButton.cloneNode(true); $button.setAttribute('aria-label', label); $button.querySelector('div[class*=label]').textContent = label; const $svg = $button.querySelector('svg'); $svg.innerHTML = svg_icon; $svg.setAttribute('viewBox', '0 0 32 32'); return $button; } function HookProperty(object, property, value) { Object.defineProperty(object, property, { value: value }); } let userAgentOriginal = window.navigator.userAgent; try { HookProperty(window.navigator, "userAgent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/999.0.0.0 Safari/537.36 Edg/999.0.0.0"); HookProperty(window.navigator, "maxTouchPoints", 10); if (NFTconfig['disableCheckNetwork'] == 1) { Object.defineProperty(window.navigator, 'connection', { get: () => undefined, }); } HookProperty(window.navigator, "standalone", true); } catch (e) { } let consoleIp; let consolePort; const patchIceCandidates = function(...arg) { // ICE server candidates const request = arg[0]; const url = (typeof request === 'string') ? request : request.url; if (url && url.endsWith('/ice') && url.includes('/sessions/') && request.method === 'GET') { const promise = originFetch(...arg); return promise.then(response => { return response.clone().text().then(text => { if (!text.length) { return response; } const options = { preferIpv6Server: NFTconfig['IPv6']==1, consoleIp: consoleIp, }; const obj = JSON.parse(text); let exchangeResponse = JSON.parse(obj.exchangeResponse); exchangeResponse = updateIceCandidates(exchangeResponse, options) obj.exchangeResponse = JSON.stringify(exchangeResponse); response.json = () => Promise.resolve(obj); response.text = () => Promise.resolve(JSON.stringify(obj)); return response; }); }); } return null; } window.fetch =async (...arg) => { let arg0 = arg[0]; let url = ""; let isRequest = false; switch (typeof arg0) { case "object": url = arg0.url; isRequest = true; break; case "string": url = arg0; break; default: break; } // 串流 if (IS_REMOTE_PLAYING && url.includes('/sessions/home')) { const clone = arg0.clone(); const headers = {}; for (const pair of clone.headers.entries()) { headers[pair[0]] = pair[1]; } headers['authorization'] = `Bearer ${RemotePlay.XHOME_TOKEN}`; const deviceInfo = RemotePlay.BASE_DEVICE_INFO; if(NFTconfig['REMOTE_PLAY_RESOLUTION']['default']=='720p'){ deviceInfo.dev.os.name = 'android'; } headers['x-ms-device-info'] = JSON.stringify(deviceInfo); const opts = { method: clone.method, headers: headers, }; if (clone.method === 'POST') { opts.body = await clone.text(); } const index = arg0.url.indexOf('.xboxlive.com'); let newUrl = 'https://wus2.gssv-play-prodxhome' + arg0.url.substring(index); arg0 = new Request(newUrl, opts); arg[0] = arg0; url = (typeof request === 'string') ? arg0 : arg0.url; // Get console IP if (url.includes('/configuration')) { const promise = originFetch(...arg); return promise.then(response => { return response.clone().json().then(obj => { console.log(obj); consoleIp = obj.serverDetails.ipAddress; consolePort = obj.serverDetails.port; response.json = () => Promise.resolve(obj); return response; }); }); } if(url.includes('/sessions/home/play')){ inGame(); document.title = document.title.replace('Fortnite','串流'); } return patchIceCandidates(...arg) || originFetch(...arg); } if (IS_REMOTE_PLAYING && url.includes('/login/user')) { try { const clone = arg0.clone(); const obj = await clone.json(); obj.offeringId = 'xhome'; arg0 = new Request('https://xhome.gssv-play-prod.xboxlive.com/v2/login/user', { method: 'POST', body: JSON.stringify(obj), headers: { 'Content-Type': 'application/json', }, }); arg[0] = arg0; } catch (e) { alert(e); console.log(e); } return originFetch(...arg); } if (IS_REMOTE_PLAYING && url.includes('/titles')) { const clone = arg0.clone(); const headers = {}; for (const pair of clone.headers.entries()) { headers[pair[0]] = pair[1]; } headers['authorization'] = `Bearer ${RemotePlay.XCLOUD_TOKEN}`; const index = arg0.url.indexOf('.xboxlive.com'); arg0 = new Request('https://wus.core.gssv-play-prod' + arg0.url.substring(index), { method: clone.method, body: await clone.text(), headers: headers, }); arg[0] = arg0; return originFetch(...arg); } //TODO // ICE server candidates const patchedIpv6 = patchIceCandidates(...arg); if (patchedIpv6) { return patchedIpv6; } if (!url.includes('xhome.') && url.indexOf('/v2/login/user') > -1) {//xgpuweb.gssv-play-prod.xboxlive.com checkIpsuc = true; let json = await arg0.json(); let body = JSON.stringify(json); // 处理免代理逻辑 if (NFTconfig['no_need_VPN_play'] == 1) { if (NFTconfig['useCustomfakeIp'] == 1 && naifeitian.isValidIP(NFTconfig['customfakeIp'])) { arg0.headers.set('x-forwarded-for', NFTconfig['customfakeIp']); } else { arg0.headers.set('x-forwarded-for', NFTconfig['regionBlock']['options'][NFTconfig['regionBlock']['blockIp']]); } } arg[0] = new Request(url, { method: arg0.method, headers: arg0.headers, body: body, }); const promise = originFetch(...arg); if (NFTconfig['useCustomfakeIp'] == 1 && naifeitian.isValidIP(NFTconfig['customfakeIp'])) { console.log('免代理成功,已设置为自定义IP【' + NFTconfig['customfakeIp']+"】"); }else{ console.log('免代理成功,已设置为【' + NFTconfig['regionBlock']['blockIp']+"】"); } return promise.then(response => { return response.clone().json().then(json => { //获取服务器列表 let newServerList = []; let currentAutoServer; json["offeringSettings"]["regions"].forEach((region) => { newServerList.push(region["name"]); if (region["isDefault"] === true) { currentAutoServer = region["name"]; } }); naifeitian.setValue("blockXcloudServerListGM", newServerList); NFTconfig['blockXcloudServerList'] = newServerList; if (NFTconfig['blockXcloudServerList'].indexOf(NFTconfig['defaultXcloudServer']) == -1) { naifeitian.setValue("defaultXcloudServerGM", ""); NFTconfig['defaultXcloudServer'] = ""; NFTconfig['blockXcloudServer'] = 0; naifeitian.setValue("blockXcloudServerGM", 0); } if (NFTconfig['blockXcloudServer'] == 1) { console.log('修改服务器开始'); json["offeringSettings"]["allowRegionSelection"] = true; let selectedServer = NFTconfig['defaultXcloudServer']; if (selectedServer !== "Auto" && newServerList.includes(selectedServer)) { json["offeringSettings"]["regions"].forEach((region) => { if (region["name"] === selectedServer) { region["isDefault"] = true; } else { region["isDefault"] = false; } }); } console.log('修改服务器结束'); } try { json["offeringSettings"]["regions"].forEach((region) => { if(region.isDefault){ StreamBadges.region = region.name; throw new Error(); } }); } catch(e) {} response.json = () => Promise.resolve(json); return response; }); }); } else if (url.indexOf('/cloud/play') > -1) { inGame(); const clone = arg0.clone(); const body = await clone.json(); if (NFTconfig['chooseLanguage'] == 1) { let selectedLanguage = NFTconfig['xcloud_game_language']; if (selectedLanguage == 'Auto') { let parts=window.location.pathname.split('/'); let pid = parts[parts.length-1]; try { let res = await fetch( "https://catalog.gamepass.com/products?market=US&language=en-US&hydration=PCInline", { "headers": { "content-type": "application/json;charset=UTF-8", }, "body": "{\"Products\":[\"" + pid + "\"]}", "method": "POST", "mode": "cors", "credentials": "omit" }); let jsonObj = await res.json(); let languageSupport = jsonObj["Products"][pid]["LanguageSupport"] for (let language of Object.keys(default_language_list)) { if (default_language_list[language] in languageSupport) { selectedLanguage = default_language_list[language]; break; } } if (selectedLanguage == 'Auto') { //防止接口没有返回支持语言 selectedLanguage = NFTconfig['IfErrUsedefaultGameLanguage']; console.log("使用次选语言"); } } catch (e) { } } console.log('语言已设置:【' + selectedLanguage+'】'); body.settings.locale = selectedLanguage; } body.settings.osName = NFTconfig['high_bitrate'] == 1 ? 'windows' : 'android'; const newRequest = new Request(arg0, { body: JSON.stringify(body), }); arg[0] = newRequest; return originFetch(...arg); } else if (url.endsWith('/configuration') && url.includes('/sessions/cloud/') && NFTconfig['autoOpenOC'] == 1 && NFTconfig['disableTouchControls'] == 0) { // Enable CustomTouchOverlay return new Promise((resolve, reject) => { originFetch(...arg).then(res => { res.json().then(json => { // console.error(json); let inputOverrides = JSON.parse(json.clientStreamingConfigOverrides || '{}') || {}; inputOverrides.inputConfiguration = { enableTouchInput: true, maxTouchPoints: 10, enableVibration:true }; json.clientStreamingConfigOverrides = JSON.stringify(inputOverrides); let cdom = $('#BabylonCanvasContainer-main').children(); if (cdom.length > 0) { canShowOC = false; } else { canShowOC = true; } let body = JSON.stringify(json); let newRes = new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers }) resolve(newRes); console.log('修改触摸成功') }).catch(err => { reject(err); }); }).catch(err => { reject(err); }); }); } else { return originFetch(...arg); } } function updateIceCandidates(candidates,options) { const pattern = new RegExp(/a=candidate:(?\d+) (?\d+) UDP (?\d+) (?[^\s]+) (?.*)/); const lst = []; for (let item of candidates) { if (item.candidate == 'a=end-of-candidates') { continue; } const groups = pattern.exec(item.candidate).groups; lst.push(groups); } if (options.preferIpv6Server) { lst.sort((a, b) => (!a.ip.includes(':') && b.ip.includes(':')) ? 1 : -1); } const newCandidates = []; let foundation = 1; lst.forEach(item => { item.foundation = foundation; item.priority = (foundation == 1) ? 10000 : 1; newCandidates.push({ 'candidate': `a=candidate:${item.foundation} 1 UDP ${item.priority} ${item.ip} ${item.the_rest}`, 'messageType': 'iceCandidate', 'sdpMLineIndex': '0', 'sdpMid': '0', }); ++foundation; }); if (options.consoleIp) { newCandidates.push({ 'candidate': `a=candidate:${newCandidates.length + 1} 1 UDP 1 ${options.consoleIp} 9002 typ host`, 'messageType': 'iceCandidate', 'sdpMLineIndex': '0', 'sdpMid': '0', }); } newCandidates.push({ 'candidate': 'a=end-of-candidates', 'messageType': 'iceCandidate', 'sdpMLineIndex': '0', 'sdpMid': '0', }); return newCandidates; } if (NFTconfig['autoOpenOC'] == 1 && NFTconfig['disableTouchControls'] == 0 && NFTconfig['autoShowTouch']) { window.RTCPeerConnection.prototype.originalCreateDataChannelGTC = window.RTCPeerConnection.prototype.createDataChannel; window.RTCPeerConnection.prototype.createDataChannel = function (...params) { let dc = this.originalCreateDataChannelGTC(...params); let paddingMsgTimeoutId = 0; if (dc.label == "message") { dc.addEventListener("message", function (de) { if (typeof (de.data) == "string") { let msgdata = JSON.parse(de.data); if (msgdata.target == "/streaming/touchcontrols/showlayoutv2") { clearTimeout(paddingMsgTimeoutId); } else if (msgdata.target == "/streaming/touchcontrols/showtitledefault") { let chkItv=setInterval(()=>{ if(canShowOC==null){return;} clearInterval(chkItv); if (!canShowOC) { clearTimeout(paddingMsgTimeoutId); } else { //这里加一个检测,否则一直1秒检测一次循环 if (msgdata.pluginHookMessage !== true) { clearTimeout(paddingMsgTimeoutId); paddingMsgTimeoutId = setTimeout(() => { dc.dispatchEvent(new MessageEvent('message', { data: '{"content":"{\\"layoutId\\":\\"\\"}","target":"/streaming/touchcontrols/showlayoutv2","type":"Message","pluginHookMessage":true}' })); }, 1000); } } },1000); } } }); } return dc; } } // 配置对象,定义每个设置项的信息 const settingsConfig = [ { label: '选择语言:', type: 'radio', name: 'chooseLanguage', display: 'block', options: [ { value: 1, text: '开', id: 'chooseLanguageOn' }, { value: 0, text: '关', id: 'chooseLanguageOff' } ], checkedValue: NFTconfig['chooseLanguage'], needHr: false }, { label: '首选语言:', type: 'radio', name: 'selectLanguage', display: NFTconfig['chooseLanguage'] === 1 ? 'block' : 'none', options: Object.keys(default_language_list).map(languageChinese => { return { value: default_language_list[languageChinese], text: languageChinese, id: default_language_list[languageChinese] }; }), checkedValue: NFTconfig['xcloud_game_language'], needHr: false }, { label: '次选语言:', type: 'radio', name: 'IfErrUsedefaultGameLanguage', display: NFTconfig['xcloud_game_language'] === 'Auto' ? 'block' : 'none', options: Object.keys(default_language_list).map(languageChinese => { if (languageChinese == '智能简繁') { return; } return { value: default_language_list[languageChinese], text: languageChinese, id: default_language_list[languageChinese] + 'ifErr' }; }), checkedValue: NFTconfig['IfErrUsedefaultGameLanguage'], needHr: true }, { label: '免代理直连:', type: 'radio', name: 'noNeedVpn', display: 'block', options: [ { value: 1, text: '开', id: 'noNeedVpnOn' }, { value: 0, text: '关', id: 'noNeedVpnOff' }, ], checkedValue: NFTconfig['no_need_VPN_play'], needHr: false }, { label: '选服:', type: 'radio', name: 'selectRegion', display: NFTconfig['no_need_VPN_play'] === 1 ? 'block' : 'none', options: Object.keys(NFTconfig['regionBlock']['options']).map(region => { return { value: NFTconfig['regionBlock']['options'][region], text: region, id: NFTconfig['regionBlock']['options'][region] }; }), checkedValue: NFTconfig['regionBlock']['options'][NFTconfig['regionBlock']['blockIp']], needHr: false }, { label: '自定义IP:', type: 'radio', name: 'customfakeIpInput', display: NFTconfig['no_need_VPN_play'] === 1 ? 'block' : 'none', value: NFTconfig['useCustomfakeIp'], needHr: true, moreDom: ` ` }, { label: '分辨率:', type: 'radio', name: 'highBitrate', display: 'block', options: [ { value: 1, text: '1080P', id: 'high_bitrateOn' }, { value: 0, text: '720P', id: 'high_bitrateOff' } ], checkedValue: NFTconfig['high_bitrate'], needHr: true }, { label: '禁止检测网络状况:', type: 'radio', name: 'disableCheckNetwork', display: 'block', options: [ { value: 1, text: '开', id: 'disableCheckNetworkOn' }, { value: 0, text: '关', id: 'disableCheckNetworkOff' } ], checkedValue: NFTconfig['disableCheckNetwork'], needHr: true }, { label: '强制触控:', type: 'radio', name: 'autoOpenOC', display: 'block', options: [ { value: 1, text: '开', id: 'autoOpenOCOn' }, { value: 0, text: '关', id: 'autoOpenOCOff' } ], checkedValue: NFTconfig['autoOpenOC'], needHr: true, moreDom: `
` }, { label: '手势显隐触控:', type: 'radio', name: 'slideToHide', display: 'block', options: [ { value: 1, text: '开', id: 'slideToHideOn' }, { value: 0, text: '关', id: 'slideToHideOff' }, ], checkedValue: NFTconfig['slideToHide'], needHr: true }, { label: '屏蔽触控:', type: 'radio', name: 'disableTouchControls', display: 'block', options: [ { value: 1, text: '开', id: 'disableTouchControlsOn' }, { value: 0, text: '关', id: 'disableTouchControlsOff' }, ], checkedValue: NFTconfig['disableTouchControls'], needHr: true }, { label: '自动全屏:', type: 'radio', name: 'autoFullScreen', display: 'block', options: [ { value: 1, text: '开', id: 'autoFullScreenOn' }, { value: 0, text: '关', id: 'autoFullScreenOff' } ], checkedValue: NFTconfig['autoFullScreen'], needHr: true }, { label: 'IPv6:', type: 'radio', name: 'IPv6server', display: 'block', options: [ { value: 1, text: '开', id: 'IPv6On' }, { value: 0, text: '关', id: 'IPv6Off' } ], checkedValue: NFTconfig['IPv6'], needHr: true } , { label: '物理服务器:', type: 'radio', name: 'blockXcloudServer', display: 'block', options: [ { value: 1, text: '开', id: 'blockXcloudServerOn' }, { value: 0, text: '关', id: 'blockXcloudServerOff' } ], checkedValue: NFTconfig['blockXcloudServer'], needHr: false }, { label: '选择服务器:', type: 'dropdown', name: 'defaultXcloudServer', display: NFTconfig['blockXcloudServer'] === 1?"block":"none", options: NFTconfig['blockXcloudServerList'], selectedValue: NFTconfig['defaultXcloudServer'], needHr: true }, { label: '挂机防踢:', type: 'radio', name: 'antiKick', display: 'block', options: [ { value: 1, text: '开', id: 'antiKickOn' }, { value: 0, text: '关', id: 'antiKickOff' } ], checkedValue: NFTconfig['antiKick'], needHr: true }, { label: '设置悬浮窗:', type: 'radio', name: 'noPopSetting', display: 'block', options: [ { value: 0, text: '显示', id: 'noPopSettingOff' }, { value: 1, text: '隐藏', id: 'noPopSettingOn' } ], checkedValue: NFTconfig['noPopSetting'], needHr: true }, { label: '开启串流功能:', type: 'radio', name: 'enableRemotePlay', display: 'block', options: [ { value: 1, text: '开', id: 'enableRemotePlayOn' }, { value: 0, text: '关', id: 'enableRemotePlayOff' } ], checkedValue: NFTconfig['enableRemotePlay'], needHr: true } ]; // 函数用于生成单个设置项的HTML function generateSettingElement(setting) { let settingHTML = ``; if (setting.type === 'radio') { if (setting.options != undefined) { settingHTML += `'; } else if (setting.type === 'text') { settingHTML += ``; } else if (setting.type === 'dropdown') { if(setting.showLable==true){ settingHTML += ``; if (setting.needHr) { settingHTML += `
` } return settingHTML; } function generateSettingsPage() { let settingsHTML = ` `; return settingsHTML; } let needrefresh = 0; function initSettingBox() { $('body').append(generateSettingsPage()); //确定 $(document).on('click', '.closeSetting1', function () { naifeitian.hideSetting(); if (needrefresh == 1) { history.go(0); } }); //开启串流 $(document).on('click', '.enableRemotePlayListener', function () { needrefresh = 1; naifeitian.setValue('enableRemotePlayGM', $(this).val()); $('.closeSetting1').text('确定'); }); //设置悬浮窗 $(document).on('click', '.noPopSettingListener', function () { naifeitian.setValue('noPopSettingGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //挂机防踢 $(document).on('click', '.antiKickListener', function () { needrefresh = 1; naifeitian.setValue('antiKickGM', $(this).val()); $('.closeSetting1').text('确定'); }); //ipv6 $(document).on('click', '.IPv6serverListener', function () { naifeitian.setValue('IPv6GM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //选择服务器change $(document).on('change', '.defaultXcloudServerListener', function () { naifeitian.setValue('defaultXcloudServerGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //物理服务器 $(document).on('click', '.blockXcloudServerListener', function () { if ($(this).val() == 0) { $('.defaultXcloudServerDom').css('display', 'none'); } else { $('.defaultXcloudServerDom').css('display', 'block'); } naifeitian.setValue('blockXcloudServerGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //自动全屏 $(document).on('click', '.autoFullScreenListener', function () { naifeitian.setValue('autoFullScreenGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //屏蔽触控 $(document).on('click', '.disableTouchControlsListener', function () { if ($(this).val() == 1) { if (!confirm("确定要屏蔽触控吗?")) { $('#disableTouchControlsOff').click(); return; } $('#autoOpenOCOff').click(); $('#slideToHideOff').click(); } needrefresh = 1; naifeitian.setValue('disableTouchControlsGM', $(this).val()); $('.closeSetting1').text('确定'); }); //自动弹出 $(document).on('change', '.autoShowTouchListener', function () { let newVal = $(this).attr('checked') == 'checked'; if (newVal) { $(this).removeAttr('checked'); } else { $(this).attr('checked'); } naifeitian.setValue('autoShowTouchGM', !newVal); needrefresh = 1; $('.closeSetting1').text('确定'); }); //手势显隐触控 $(document).on('click', '.slideToHideListener', function () { if ($(this).val() == 1) { $('#disableTouchControlsOff').click(); $('#autoOpenOCOn').click(); } naifeitian.setValue('slideToHideGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //强制触控 $(document).on('click', '.autoOpenOCListener', function () { if ($(this).val() == 0) { $('#autoShowTouchDom').css('display', 'none'); } else { $('#autoShowTouchDom').css('display', 'inline'); $('#disableTouchControlsOff').click(); } naifeitian.setValue('autoOpenOCGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //禁止检测网络 $(document).on('click', '.disableCheckNetworkListener', function () { naifeitian.setValue('disableCheckNetworkGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //分辨率 $(document).on('click', '.highBitrateListener', function () { naifeitian.setValue('high_bitrateGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //自定义ip输入框 $(document).on('blur', '.customfakeIpListener', function () { if (naifeitian.isValidIP($(this).val())) { naifeitian.setValue('customfakeIpGM', $(this).val()); } else { $(this).val(""); naifeitian.setValue('customfakeIpGM', ''); alert('IP格式错误!'); return; } needrefresh = 1; $('.closeSetting1').text('确定'); }); //选服 $(document).on('click', '.selectRegionListener', function () { if ($(this).val() == 'customfakeIp') { naifeitian.setValue('useCustomfakeIpGM', 1); $('#customfakeIpInput').css('display', 'inline'); } else { NFTconfig['regionBlock']['blockIp']=Object.keys(NFTconfig['regionBlock']['options']).find(key => NFTconfig['regionBlock']['options'][key] === $(this).val()); naifeitian.setValue('regionBlockGM', NFTconfig['regionBlock']); naifeitian.setValue('useCustomfakeIpGM', 0); $('#customfakeIpInput').css('display', 'none'); } needrefresh = 1; $('.closeSetting1').text('确定'); }); //免代理直连 $(document).on('click', '.noNeedVpnListener', function () { if ($(this).val() == 0) { $('.selectRegionDom').css('display', 'none');; $('.customfakeIpInputDom').css('display', 'none'); } else { $('.selectRegionDom').css('display', 'block'); $('.customfakeIpInputDom').css('display', 'block'); } naifeitian.setValue('no_need_VPN_playGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //智能简繁错误 $(document).on('click', '.IfErrUsedefaultGameLanguageListener', function () { naifeitian.setValue('IfErrUsedefaultGameLanguageGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //语言 $(document).on('click', '.selectLanguageListener', function () { if ($(this).val() != 'Auto') { $('.IfErrUsedefaultGameLanguageDom').css('display', 'none'); } else { $('.IfErrUsedefaultGameLanguageDom').css('display', 'block'); } naifeitian.setValue('xcloud_game_languageGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //选择语言 $(document).on('click', '.chooseLanguageListener', function () { if ($(this).val() == 0) { $('.selectLanguageDom').css('display', 'none'); $('.IfErrUsedefaultGameLanguageDom').css('display', 'none'); } else { $('.selectLanguageDom').css('display', 'block'); if (naifeitian.getValue('xcloud_game_languageGM') == 'Auto') { $('.IfErrUsedefaultGameLanguageDom').css('display', 'block'); } } naifeitian.setValue('chooseLanguageGM', $(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); } function initSlideHide(){ if(NFTconfig['slideToHide']==1){ var gestureArea = $("
"); gestureArea.attr("id", "touchControllerEventArea"); $(document.documentElement).append(gestureArea); gestureArea = $("#touchControllerEventArea"); let startX, startY, endX, endY; let threshold = 130; // 手势滑动的阈值 gestureArea.on("touchstart", function (e) { startX = e.originalEvent.touches[0].clientX; startY = e.originalEvent.touches[0].clientY; }); gestureArea.on("touchmove", function (e) { endX = e.originalEvent.touches[0].clientX; endY = e.originalEvent.touches[0].clientY; }); gestureArea.on("touchend", function (e) { if (startX !== undefined && startY !== undefined && endX !== undefined && endY !== undefined) { const deltaX = endX - startX; const deltaY = endY - startY; if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > threshold) { if (deltaX < 0) { // 左滑 $('#BabylonCanvasContainer-main').css('display','none'); $('#MultiTouchSurface').css('display','none'); e.preventDefault(); } else { // 右滑 $('#BabylonCanvasContainer-main').css('display','block'); $('#MultiTouchSurface').css('display','block'); e.preventDefault(); } } } }); } } async function fetchData() { try { const response = await fetch("https://greasyfork.org/zh-CN/scripts/455741-xbox-cloud-gaming%E4%BC%98%E5%8C%96%E6%95%B4%E5%90%88/versions"); const data = await response.text(); let historyVersion = $(data).find('.history_versions')[0]; let hli = $($(historyVersion).find('li .version-number > a')[0]); let version = hli.text(); if(nftxboxversion!=version){ $('head').append(''); } } catch (error) { //console.error('Fetch error:', error); } } $(document).ready(function () { //检测Thank you for your interest let checkTYFYIInterval=setInterval(()=>{ if(checkIpsuc){ clearInterval(checkTYFYIInterval); return; } var title = $("[class*='UnsupportedMarketPage-module__title']"); if(title.length>0){ console.log("脚本检测到免代理没有成功,自动刷新中"); title.text("脚本检测到免代理没有成功,自动刷新中"); history.go(0); clearInterval(checkTYFYIInterval); } },5000); setTimeout(function () { if (NFTconfig['noPopSetting'] == 0) { $('body').append(`
⚙️ 设置
`); $(document).on('click', '#popSetting', function () { naifeitian.showSetting(); }); } fetchData(); initSettingBox(); updateVideoPlayerCss(); StreamStats.render(); setupVideoSettingsBar(); initSlideHide(); }, 2000); }); let timer; let mousehidding = false; $(document).mousemove(function () { if (mousehidding) { mousehidding = false; return; } if (timer) { clearTimeout(timer); timer = 0; } $('html').css({ cursor: '' }); timer = setTimeout(function () { mousehidding = true; $('html').css({ cursor: 'none' }); }, 2000); }); $(window).on('popstate', function () { exitGame(); }); let _pushState = window.history.pushState; window.history.pushState = function () { if (NFTconfig['noPopSetting'] == 0) { if (arguments[2].substring(arguments[2].length, arguments[2].length - 5) == '/play') { $('#popSetting').css('display', 'block'); } else { $('#popSetting').css('display', 'none'); } } if(arguments[2].indexOf("/play/games/")>-1){ let timeout=0; let checkPlayInterval= setInterval(()=>{ var playButtons = $("[class*='PlayButton-module__playButton'][disabled]"); if(playButtons.length>0){ playButtons.text("该游戏无法在"+[NFTconfig['regionBlock']['blockIp']]+"游玩,请使用脚本切换其他服尝试"); clearInterval(checkPlayInterval); } if(timeout==10){ clearInterval(checkPlayInterval); } timeout++; },1333); } if((!arguments[2].includes("/play/launch/") && !arguments[2].includes('/remote-play')) ){ exitGame(); } if(arguments[2].includes("/https")){ if(window.location.href.includes("/remote-play")){ return; } setTimeout(()=>{history.go(-1)},10); } setTimeout(RemotePlay.detect, 10); return _pushState.apply(this, arguments); } window.onpopstate = function (event) { if (event.state) { if (window.location.href.slice(-5) == '/play') { exitGame(); } } }; function getVideoPlayerFilterStyle() { const filters = []; const clarity = NFTconfig['VIDEO_CLARITY']['default']; if (clarity != 0) { const level = 7 - (clarity - 1); // 5,6,7 const matrix = `0 -1 0 -1 ${level} -1 0 -1 0`; document.getElementById('better-xcloud-filter-clarity-matrix').setAttributeNS(null, 'kernelMatrix', matrix); filters.push(`url(#better-xcloud-filter-clarity)`); } const saturation = NFTconfig['VIDEO_SATURATION']['default']; if (saturation != 100) { filters.push(`saturate(${saturation}%)`); } const contrast = NFTconfig['VIDEO_CONTRAST']['default']; if (contrast != 100) { filters.push(`contrast(${contrast}%)`); } const brightness = NFTconfig['VIDEO_BRIGHTNESS']['default']; if (brightness != 100) { filters.push(`brightness(${brightness}%)`); } return filters.join(' '); } function updateVideoPlayerCss() { let $elm = document.getElementById('better-xcloud-video-css'); if (!$elm) { const CE = createElement; $elm = CE('style', { id: 'better-xcloud-video-css' }); document.documentElement.appendChild($elm); // Setup SVG filters const $svg = CE('svg', { 'id': 'better-xcloud-video-filters', 'xmlns': 'http://www.w3.org/2000/svg', 'class': 'better-xcloud-gone', }, CE('defs', { 'xmlns': 'http://www.w3.org/2000/svg' }, CE('filter', { 'id': 'better-xcloud-filter-clarity', 'xmlns': 'http://www.w3.org/2000/svg' }, CE('feConvolveMatrix', { 'id': 'better-xcloud-filter-clarity-matrix', 'order': '3', 'xmlns': 'http://www.w3.org/2000/svg' })) ) ); document.documentElement.appendChild($svg); } let filters = getVideoPlayerFilterStyle(); let css = ''; if (filters) { css += `filter: ${filters} !important;`; } if (NFTconfig['video_stretch'].default == 'fill') { css += 'object-fit: fill !important;'; } if (NFTconfig['video_stretch'].default == 'setting') { css += `transform: scaleX(` + (NFTconfig['video_stretch_x_y'].x * 1 + 1) + `) scaleY(` + (NFTconfig['video_stretch_x_y'].y * 1 + 1) + `) !important;`; } if (css) { css = `#game-stream video {${css}}`; } $elm.textContent = css; } function injectVideoSettingsButton() { const $screen = document.querySelector('#PageContent section[class*=PureScreens]'); if (!$screen) { return; } if ($screen.xObserving) { return; } $screen.xObserving = true; const $quickBar = document.querySelector('.better-xcloud-quick-settings-bar'); const $parent = $screen.parentElement; const hideQuickBarFunc = e => { e.stopPropagation(); if (e.target != $parent && e.target.id !== 'MultiTouchSurface' && !e.target.querySelector('#BabylonCanvasContainer-main')) { return; } // Hide Quick settings bar $quickBar.style.display = 'none'; $parent.removeEventListener('click', hideQuickBarFunc); $parent.removeEventListener('touchstart', hideQuickBarFunc); if (e.target.id === 'MultiTouchSurface') { e.target.removeEventListener('touchstart', hideQuickBarFunc); } } const observer = new MutationObserver(mutationList => { mutationList.forEach(item => { if (item.type !== 'childList') { return; } item.addedNodes.forEach(async node => { if (!node.className || !node.className.startsWith('StreamMenu')) { return; } const $orgButton = node.querySelector('div > div > button'); if (!$orgButton) { return; } // Create Video Settings button const $btnVideoSettings = cloneStreamMenuButton($orgButton, '视频调整', ICON_VIDEO_SETTINGS); $btnVideoSettings.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); // Close HUD $btnCloseHud.click(); // Show Quick settings bar $quickBar.style.display = 'flex'; $parent.addEventListener('click', hideQuickBarFunc); $parent.addEventListener('touchstart', hideQuickBarFunc); const $touchSurface = document.getElementById('MultiTouchSurface'); $touchSurface && $touchSurface.style.display != 'none' && $touchSurface.addEventListener('touchstart', hideQuickBarFunc); }); // Add button at the beginning $orgButton.parentElement.insertBefore($btnVideoSettings, $orgButton.parentElement.firstChild); // Hide Quick bar when closing HUD const $btnCloseHud = document.querySelector('button[class*=StreamMenu-module__backButton]'); $btnCloseHud.addEventListener('click', e => { $quickBar.style.display = 'none'; }); // Create Stream Stats button const $btnStreamStats = cloneStreamMenuButton($orgButton, '流监控', ICON_STREAM_STATS); $btnStreamStats.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); // Toggle Stream Stats StreamStats.toggle(); }); // Insert after Video Settings button $orgButton.parentElement.insertBefore($btnStreamStats, $btnVideoSettings); //桥 const $menu = document.querySelector('div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]'); //const streamBadgesElement = await StreamBadges.render(); $menu.appendChild(await StreamBadges.render()); //$menu.insertAdjacentElement('afterend', streamBadgesElement); }); }); }); observer.observe($screen, { subtree: true, childList: true }); } let transitionComplete = true; let SildeopenBlock=0; function patchVideoApi() { // Show video player when it's ready let showFunc; showFunc = function () { this.removeEventListener('playing', showFunc); if (!this.videoWidth) { return; } onStreamStarted(this); STREAM_WEBRTC?.getStats().then(stats => { if (NFTconfig['STATS_SHOW_WHEN_PLAYING']['default']) { StreamStats.start(); } }); let chkDom=setInterval(()=>{ if($('#StreamHud button').length>0){ clearInterval(chkDom); if($('#StreamHud').css('left')=='0px' && NFTconfig['STATS_SLIDE_OPEN']['default']){ StreamStats.start(); } if (SildeopenBlock == 0) { SildeopenBlock=1; $(document).on('click', '.'+ $($('#StreamHud button')[$('#StreamHud button').length-1]).attr('class'), function () { transitionComplete=true; }); $(document).on('transitionend', '#StreamHud', function () { if(!NFTconfig['STATS_SLIDE_OPEN']['default']){return;} if(!transitionComplete){return;} if($('#StreamHud').css('left')=='0px'){ StreamStats.start(); }else{ StreamStats.stop(); } transitionComplete=false; }); } } },2000); } HTMLMediaElement.prototype.orgPlay = HTMLMediaElement.prototype.play; HTMLMediaElement.prototype.play = function () { if (letmeOb && NFTconfig['antiKick'] == 1) { const divElement = $('div[data-testid="ui-container"]')[0]; const observer = new MutationObserver(function (mutations) { try { mutations.forEach(function (mutation) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(function (addedNode) { let btn = $(addedNode).find('button[data-auto-focus="true"]'); if ($(btn).length > 0 && btn.parent().children().length == 1) { $(btn).click(); throw new Error("巴啦啦能量-呼尼拉-魔仙变身!"); } }); } }); } catch (e) { } }); setTimeout(() => { observer.observe(divElement, { childList: true, subtree: true }); console.log('antiKick已部署'); }, 1000 * 20); letmeOb = false; } this.addEventListener('playing', showFunc); injectVideoSettingsButton(); return this.orgPlay.apply(this); }; } function onStreamStarted($video) { StreamBadges.resolution = {width: $video.videoWidth, height: $video.videoHeight}; StreamBadges.startTimestamp = +new Date; // Get battery level try { navigator.getBattery && navigator.getBattery().then(bm => { StreamBadges.startBatteryLevel = Math.round(bm.level * 100); }); STREAM_WEBRTC.getStats().then(stats => { const allVideoCodecs = {}; let videoCodecId; const allAudioCodecs = {}; let audioCodecId; const allCandidates = {}; let candidateId; stats.forEach(stat => { if (stat.type == 'codec') { const mimeType = stat.mimeType.split('/'); if (mimeType[0] === 'video') { // Store all video stats allVideoCodecs[stat.id] = stat; } else if (mimeType[0] === 'audio') { // Store all audio stats allAudioCodecs[stat.id] = stat; } } else if (stat.type === 'inbound-rtp' && stat.packetsReceived > 0) { // Get the codecId of the video/audio track currently being used if (stat.kind === 'video') { videoCodecId = stat.codecId; } else if (stat.kind === 'audio') { audioCodecId = stat.codecId; } } else if (stat.type === 'candidate-pair' && stat.packetsReceived > 0 && stat.state === 'succeeded') { candidateId = stat.remoteCandidateId; } else if (stat.type === 'remote-candidate') { allCandidates[stat.id] = stat.address; } }); // Get video codec from codecId if (videoCodecId) { const videoStat = allVideoCodecs[videoCodecId]; const video = { codec: videoStat.mimeType.substring(6), }; if (video.codec === 'H264') { const match = /profile-level-id=([0-9a-f]{6})/.exec(videoStat.sdpFmtpLine); video.profile = match ? match[1] : null; } StreamBadges.video = video; } // Get audio codec from codecId if (audioCodecId) { const audioStat = allAudioCodecs[audioCodecId]; StreamBadges.audio = { codec: audioStat.mimeType.substring(6), bitrate: audioStat.clockRate, } } // Get server type if (candidateId) { //console.log(candidateId, allCandidates); StreamBadges.ipv6 = allCandidates[candidateId].includes(':'); } }); } catch(e) {} } function moveCodecToIndex(array, currentIndex, targetIndex, element) { array.splice(currentIndex, 1); array.splice(targetIndex, 0, element); } function customizeRtcCodecs() { if (typeof RTCRtpTransceiver === 'undefined' || !('setCodecPreferences' in RTCRtpTransceiver.prototype)) { return false; } RTCRtpTransceiver.prototype.originalSetCodecPreferences = RTCRtpTransceiver.prototype.setCodecPreferences; RTCRtpTransceiver.prototype.setCodecPreferences = function(codecs) { let a=[]; let b=[]; let c=[]; let d=[]; codecs.slice().forEach((item)=>{ if(item.mimeType=='video/H264'){ if(item.sdpFmtpLine.indexOf('id=4d')>-1){ a.push(item); }else if(item.sdpFmtpLine.indexOf('id=42e')>-1){ b.push(item); }else if(item.sdpFmtpLine.indexOf('id=420')>-1){ c.push(item); }else{ d.push(item); } }else{ d.push(item); } }); const customizedCodecs=a.concat(b,c,d); try { this.originalSetCodecPreferences.apply(this, [customizedCodecs]); console.log("编解码偏好配置成功"); } catch (error) { console.log("无法修改编解码配置,将使用默认设置"); this.originalSetCodecPreferences.apply(this, [codecs]); } } } customizeRtcCodecs(); patchVideoApi(); let mslogotimeOut = 0; function mslogoClickevent(mslogoInterval, s) { let mslogodom = $($('header>div>div>button')[1]); if (mslogodom.length > 0) { clearInterval(mslogoInterval); mslogodom = mslogodom.next(); if (mslogodom.text() == ("⚙️ 设置" + nftxboxversion)) { return; } mslogodom.removeAttr('href'); mslogodom.css("color", 'white'); mslogodom.text("⚙️ 设置" + nftxboxversion); mslogodom.click(() => { naifeitian.showSetting(); }); setTimeout(() => { mslogoClickevent(mslogoInterval) }, 5000); } mslogotimeOut = mslogotimeOut + 1; if (mslogotimeOut > 10) { mslogotimeOut = 0; clearInterval(mslogoInterval); } } let mslogoInterval = setInterval(() => { mslogoClickevent(mslogoInterval, 3000); }, 1000); class Dialog { constructor(title, className, $content, onClose) { const CE = createElement; // Create dialog overlay this.$overlay = document.querySelector('.bx-dialog-overlay'); if (!this.$overlay) { this.$overlay = CE('div', {'class': 'bx-dialog-overlay bx-gone'}); document.documentElement.appendChild(this.$overlay); } let $close; this.onClose = onClose; this.$dialog = CE('div', {'class': `bx-dialog ${className} bx-gone`}, CE('b', {}, title), CE('div', {'class': 'bx-dialog-content'}, $content), $close = CE('button', {}, "关闭")); $close.addEventListener('click', e => { this.hide(e); }); document.documentElement.appendChild(this.$dialog); } show() { this.$dialog.classList.remove('bx-gone'); this.$overlay.classList.remove('bx-gone'); } hide(e) { this.$dialog.classList.add('bx-gone'); this.$overlay.classList.add('bx-gone'); this.onClose && this.onClose(e); } toggle() { this.$dialog.classList.toggle('bx-gone'); this.$overlay.classList.toggle('bx-gone'); } } let REMOTE_PLAY_CONFIG; let IS_REMOTE_PLAYING; class RemotePlay { static XCLOUD_TOKEN; static XHOME_TOKEN; static #CONSOLES; static #STATE_LABELS = { 'On': "已开机", 'Off': "已关机", 'ConnectedStandby':"待机中", 'Unknown': "未知", }; static get BASE_DEVICE_INFO() { return { appInfo: { env: { clientAppId: window.location.host, clientAppType: 'browser', clientAppVersion: '21.1.98', clientSdkVersion: '8.5.3', httpEnvironment: 'prod', sdkInstallId: '', }, }, dev: { displayInfo: { dimensions: { widthInPixels: 1920, heightInPixels: 1080, }, pixelDensity: { dpiX: 1, dpiY: 1, }, }, hw: { make: 'Microsoft', model: 'unknown', sdktype: 'web', }, os: { name: 'windows', ver: '22631.2715', platform: 'desktop', }, browser: { browserName: 'chrome', browserVersion: '119.0', }, }, }; } static #dialog; static #$content; static #$consoles; static #initialize() { if (RemotePlay.#$content) { return; } const CE = createElement; RemotePlay.#$content = CE('div', {}, "获取控制台列表"); RemotePlay.#dialog = new Dialog(("串流"), '', RemotePlay.#$content); RemotePlay.#getXhomeToken(() => { RemotePlay.#getConsolesList(() => { console.log(RemotePlay.#CONSOLES); RemotePlay.#renderConsoles(); }); }); } static #renderConsoles() { const CE = createElement; const $fragment = document.createDocumentFragment(); if (!RemotePlay.#CONSOLES || RemotePlay.#CONSOLES.length === 0) { $fragment.appendChild(CE('span', {}, "未找到主机")); } else { const $settingNote = CE('p', {}); const resolutions = [1080, 720]; const currentResolution = NFTconfig['REMOTE_PLAY_RESOLUTION']['default']; const $resolutionSelect = CE('select', {}); for (const resolution of resolutions) { const value = `${resolution}p`; const $option = CE('option', {'value': value}, value); if (currentResolution === value) { $option.selected = true; } $resolutionSelect.appendChild($option); } $resolutionSelect.addEventListener('change', e => { const value = $resolutionSelect.value; $settingNote.textContent = value === '1080p' ? '✅ ' + "可串流xbox360游戏" : '❌ ' + "可串流xbox360游戏"; NFTconfig['REMOTE_PLAY_RESOLUTION']['default']=value; naifeitian.setValue(NFTconfig['REMOTE_PLAY_RESOLUTION']['name'], NFTconfig['REMOTE_PLAY_RESOLUTION']); }); $resolutionSelect.dispatchEvent(new Event('change')); const $qualitySettings = CE('div', {'class': 'bx-remote-play-settings'}, CE('div', {}, CE('label', {}, "目标分辨率", $settingNote), $resolutionSelect, ) ); $fragment.appendChild($qualitySettings); } for (let con of RemotePlay.#CONSOLES) { let $connectButton; 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) ), CE('div', {'class': 'bx-remote-play-power-state'}, RemotePlay.#STATE_LABELS[con.powerState]), ), $connectButton = CE('button', {'class': 'bx-primary-button bx-no-margin'}, "连接"), ); $connectButton.addEventListener('click', e => { REMOTE_PLAY_CONFIG = { serverId: con.serverId, }; window.BX_REMOTE_PLAY_CONFIG = REMOTE_PLAY_CONFIG; const url = window.location.href.substring(0, 31) + '/launch/fortnite/BT5P2X999VH2#remote-play'; const $pageContent = document.getElementById('PageContent'); const $anchor = CE('a', {href: url, class: 'bx-hidden', style: 'position:absolute;top:-9990px;left:-9999px'}, ''); $anchor.addEventListener('click', e => { setTimeout(() => { $pageContent.removeChild($anchor); }, 1000); }); $pageContent.appendChild($anchor); $anchor.click(); RemotePlay.#dialog.hide(); }); $fragment.appendChild($child); } RemotePlay.#$content.parentElement.replaceChild($fragment, RemotePlay.#$content); } static detect() { IS_REMOTE_PLAYING = window.location.pathname.includes('/remote-play') || window.location.hash.startsWith('#remote-play'); if (IS_REMOTE_PLAYING) { window.BX_REMOTE_PLAY_CONFIG = REMOTE_PLAY_CONFIG; // 移除 /launch/... window.history.replaceState({}, '', 'https://www.xbox.com/' + location.pathname.substring(1, 6) + '/remote-play'); } else { window.BX_REMOTE_PLAY_CONFIG = null; } } static #getXhomeToken(callback) { if (RemotePlay.XHOME_TOKEN) { callback(); return; } const GSSV_TOKEN = JSON.parse(localStorage.getItem('xboxcom_xbl_user_info')).tokens['http://gssv.xboxlive.com/'].token; fetch('https://xhome.gssv-play-prod.xboxlive.com/v2/login/user', { method: 'POST', body: JSON.stringify({ offeringId: 'xhome', token: GSSV_TOKEN, }), headers: { 'Content-Type': 'application/json; charset=utf-8', }, }).then(resp => resp.json()) .then(json => { RemotePlay.XHOME_TOKEN = json.gsToken; callback(); }); } static #getConsolesList(callback) { if (RemotePlay.#CONSOLES) { callback(); return; } fetch('https://wus2.gssv-play-prodxhome.xboxlive.com/v6/servers/home?mr=50', { method: 'GET', headers: { 'Authorization': `Bearer ${RemotePlay.XHOME_TOKEN}`, }, }).then(resp => resp.json()) .then(json => { RemotePlay.#CONSOLES = json.results; callback(); }); } static showDialog() { RemotePlay.#initialize(); RemotePlay.#dialog.show(); } } function bindmslogoevent() { let divElement = $('#gamepass-root > div > div'); if (divElement.length == 0) { setTimeout(() => { bindmslogoevent(); }, 2333); return; } divElement = divElement.get(0); let mslogodom = $(divElement).children('header').find('a[href]'); if (mslogodom.length == 0) { setTimeout(() => { bindmslogoevent(); }, 2333); return; } if (mslogodom.length > 0) { mslogodom = $(mslogodom.get(0)); } let linkElement = $("a:contains('⚙️ 设置"+ nftxboxversion+"')"); for(let i=0;i-1){ return; } } mslogodom.removeAttr('href'); mslogodom.css("color", 'white'); mslogodom.text("⚙️ 设置" + nftxboxversion); mslogodom.click(() => { naifeitian.showSetting(); }); if(NFTconfig['enableRemotePlay']==1){ let remotePlayBtn=$('.remote-play-button'); if(remotePlayBtn.length>0){return;} //添加串流按钮 var targetElement = $("[title*='Account Settings']"); var newButton = $("