// ==UserScript== // @name Xbox CLoud Gaming优化整合 // @name:zh-CN Xbox CLoud Gaming优化整合 // @namespace http://tampermonkey.net/xbox/nft // @version 3.9.3 // @author 奈非天 // @license MIT // @match https://www.xbox.com/*/play* // @run-at document-start // @grant unsafeWindow // @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... let nftxboxversion = 'v3.9.3'; 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'); }); const originFetch = fetch; let regionsMenuItemList = []; let languageMenuItemList = []; let letmeOb = true; 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() { 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(){ 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; } 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); } }); }); } 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 windowCtx = self.window; if(NFTconfig['enableRemotePlay']==0){ if (self.unsafeWindow) { windowCtx = self.unsafeWindow; } } let userAgentOriginal = window.navigator.userAgent; try { HookProperty(window.navigator, "userAgent",NFTconfig['CustomUAUser']); HookProperty(window.navigator, "maxTouchPoints", 10); if (NFTconfig['disableCheckNetwork'] == 1) { Object.defineProperty(window.navigator, 'connection', { get: () => undefined, }); } HookProperty(window.navigator, "standalone", true); } catch (e) { } windowCtx.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('/home/play')) { const clone = arg0.clone(); const cloneBody = await clone.json(); cloneBody.settings.osName = 'windows'; // Clone headers const headers = {}; for (const pair of clone.headers.entries()) { headers[pair[0]] = pair[1]; } 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); headers['authorization'] = `Bearer ${RemotePlay.XHOME_TOKEN}`; arg0 = new Request('https://wus2.gssv-play-prodxhome.xboxlive.com/v5/sessions/home/play', { method: 'POST', body: JSON.stringify(cloneBody), headers: headers, }); arg[0] = arg0; inGame(); document.title = document.title.replace('Fortnite','串流'); return originFetch(...arg); }else 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); }else 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); }else if (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 opts = { method: clone.method, headers: headers, }; if (clone.method === 'POST') { opts.body = await clone.text(); } const index = arg0.url.indexOf('.xboxlive.com'); arg0 = new Request('https://wus2.gssv-play-prodxhome' + arg0.url.substring(index), opts); arg[0] = arg0; return originFetch(...arg); } else if (!url.includes('xhome.') && url.indexOf('/v2/login/user') > -1) {//xgpuweb.gssv-play-prod.xboxlive.com return new Promise((resolve, reject) => { if (isRequest && arg0.method == "POST") { arg0.json().then(json => { let body = JSON.stringify(json); if (NFTconfig['no_need_VPN_play'] == 1) { console.log('免代理开始'); if (NFTconfig['customfakeIp'] == 1 && naifeitian.isValidIP(NFTconfig['customfakeIp'])) { arg[0].headers.set('x-forwarded-for', NFTconfig['customfakeIp']); console.log('自定义IP:' + NFTconfig['customfakeIp']); } else { arg[0].headers.set('x-forwarded-for', NFTconfig['fakeIp']); } } arg[0] = new Request(url, { method: arg0.method, headers: arg0.headers, body: body, }); originFetch(...arg).then(res => { console.log('免代理结束'); res.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) {} let body = JSON.stringify(json); let newRes = new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers }) resolve(newRes); }).catch(err => { reject(err); }); }).catch(err => { reject(err); }); }); } else { console.error("[ERROR] Not a request."); return originFetch(...arg); } }); } else if (url.indexOf('/cloud/play') > -1) { inGame(); if (NFTconfig['chooseLanguage'] == 1) { return new Promise(async (resolve, reject) => { console.log('语言开始'); let selectedLanguage = NFTconfig['xcloud_game_language']; console.log('语言选择:' + selectedLanguage); 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']; } } catch (e) { } } if (isRequest && arg0.method == "POST") { arg0.json().then(json => { json["settings"]["locale"] = selectedLanguage; json["settings"]["osName"] = NFTconfig['high_bitrate'] == 1 ? 'windows' : 'android'; let body = JSON.stringify(json); arg[0] = new Request(url, { method: arg0.method, headers: arg0.headers, body: body, mode: arg0.mode, credentials: arg0.credentials, cache: arg0.cache, redirect: arg0.redirect, referrer: arg0.referrer, integrity: arg0.integrity }); originFetch(...arg).then(res => { console.log(`语言结束, 选择语言: ${selectedLanguage}.`) resolve(res); }).catch(err => { reject(err); }); }); } else { console.error("[ERROR] Not a request."); return originFetch(...arg); } }); } else { return originFetch(...arg); } } else if (url.indexOf('/configuration') > -1 && NFTconfig['autoOpenOC'] == 1 && NFTconfig['disableTouchControls'] == 0) { // Enable CustomTouchOverlay console.log('修改触摸开始') 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) { NFTconfig['canShowOC'] = false; } else { NFTconfig['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 if (NFTconfig['IPv6']==1 && url.indexOf('/ice') > -1 && url.indexOf('/sessions/cloud')>-1 && arg0.method == "GET") {//https://ckr.core.gssv-play-prod.xboxlive.com/v5/sessions/cloud/EC9AA551-2EF7-4924-8B69-0FDB85AE8C6A/ice return originFetch(...arg).then(response => { return response.clone().text().then(text => { if (!text.length) { return response; } const obj = JSON.parse(text); let exchangeResponse = JSON.parse(obj.exchangeResponse); exchangeResponse = updateIceCandidates(exchangeResponse) obj.exchangeResponse = JSON.stringify(exchangeResponse); response.json = () => Promise.resolve(obj); response.text = () => Promise.resolve(JSON.stringify(obj)); return response; }); }); } else { return originFetch(...arg); } } function updateIceCandidates(candidates) { 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); } lst.sort((a, b) => (a.ip.includes(':') || a.ip > b.ip) ? -1 : 1); const newCandidates = []; let foundation = 1; lst.forEach(item => { item.foundation = foundation; item.priority = (foundation == 1) ? 100 : 1; newCandidates.push({ 'candidate': `a=candidate:${item.foundation} 1 UDP ${item.priority} ${item.ip} ${item.the_rest}`, 'messageType': 'iceCandidate', 'sdpMLineIndex': '0', 'sdpMid': '0', }); ++foundation; }); newCandidates.push({ 'candidate': 'a=end-of-candidates', 'messageType': 'iceCandidate', 'sdpMLineIndex': '0', 'sdpMid': '0', }); return newCandidates; } function checkCodec(){ let video_quality= naifeitian.getValue('video_qualityGM'); let codecs=RTCRtpReceiver.getCapabilities('video').codecs; let codesOptions=['默认']; const codecProfileMap = {"高": "4d","中": "42e","低": "420"}; codecs.forEach((codec, index) => { if (codec.mimeType === 'video/H264') { for (let key in codecProfileMap) { if (codec.sdpFmtpLine.includes(codecProfileMap[key])) { codesOptions.push(codec.mimeType.substring(6)+key); break; } } }else{ codesOptions.push(codec.mimeType.substring(6)); } }); codesOptions = [...new Set(codesOptions)]; let sortOrder = ['默认','AV1', 'VP9', 'H265', 'VP8','H264高','H264中','H264低','flexfec-03','ulpfec','rtx','red']; const customSort = (a, b) => { const indexOfA = sortOrder.indexOf(a); const indexOfB = sortOrder.indexOf(b); if (indexOfA === -1) { return 1; } if (indexOfB === -1) { return -1; } return indexOfA - indexOfB; }; codesOptions.sort(customSort); video_quality['options']=codesOptions; if (!video_quality['options'].includes(video_quality['default'])) { video_quality['default']="默认"; } NFTconfig['video_quality']=video_quality; naifeitian.setValue('video_qualityGM',video_quality); } checkCodec(); 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") { if (!NFTconfig['canShowOC']) { clearTimeout(paddingMsgTimeoutId); } else { 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); } } } } }); } 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['regionsList']).map(region => { return { value: NFTconfig['regionsList'][region], text: region, id: NFTconfig['regionsList'][region] }; }), checkedValue: NFTconfig['fakeIp'], needHr: false }, { label: '自定义IP:', type: 'radio', name: 'customfakeIpInput', display: NFTconfig['no_need_VPN_play'] === 1 ? 'block' : 'none', value: NFTconfig['customfakeIp'], 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: '视频编解码偏好:', showLable:true, type: 'dropdown', name: 'video_quality', display: "block", options: NFTconfig['video_quality']['options'], selectedValue: NFTconfig['video_quality']['default'], 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: 'User-Agent:', showLable:true, type: 'dropdown', css:'width:90%', name: 'User-Agent', display: "block", options: NFTconfig['CustomUA']['options'], optionsCss:'float:right', selectedValue: NFTconfig['CustomUA']['default'], needHr: true, moreDom: `
` }, { 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('确定'); }); //ua输入框 $(document).on('blur', '.customUAuserListener', function () { if($(this).attr('readonly')=='readonly'){return;} if($(this).val()==""){ alert("请输入ua"); return; } NFTconfig['CustomUA']['options']['自定义']=$(this).val(); naifeitian.setValue('CustomUAGM',NFTconfig['CustomUA']); naifeitian.setValue('CustomUAUserGM',$(this).val()); needrefresh = 1; $('.closeSetting1').text('确定'); }); //ua $(document).on('change', '.User-AgentListener', function () { if($(this).val()=='自定义'){ $('#customUAuser').removeAttr('readonly'); $('#customUAuser').removeAttr('disable'); }else{ $("#customUAuser").prop('readonly', true); $("#customUAuser").prop('disable', true); } naifeitian.setValue('CustomUAUserGM',NFTconfig['CustomUA']['options'][$(this).val()]); NFTconfig['CustomUA']['default']=$(this).val(); naifeitian.setValue('CustomUAGM',NFTconfig['CustomUA']); $('#customUAuser').val(NFTconfig['CustomUA']['options'][NFTconfig['CustomUA']['default']]) needrefresh = 1; $('.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('change', '.video_qualityListener', function () { NFTconfig['video_quality']['default']=$(this).val(); naifeitian.setValue('video_qualityGM', NFTconfig['video_quality']); 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 { naifeitian.setValue('fakeIpGM', $(this).val()); 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 () { setTimeout(function () { let popCss = ` #popSetting { width: 76px; height: 33px; background: #fff; position: absolute; top: 30%; cursor: pointer; box-sizing: border-box; background-size: 100% 100%; overflow: hidden; font-family: Arial; font-size: 18px; line-height: 30px; font-weight: bold; color: #000000bf; border: 2px solid; border-radius: 10px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none ; } .better-xcloud-hidden { visibility: hidden !important; } .better-xcloud-stats-bar { display: block; user-select: none; position: fixed; top: 0; background-color: #000; color: #fff; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9rem; padding-left: 8px; z-index: 1000; text-wrap: nowrap; } .better-xcloud-stats-bar[data-position=top-left] { left: 20px; } .better-xcloud-stats-bar[data-position=top-right] { right: 0; } .better-xcloud-stats-bar[data-position=top-center] { transform: translate(-50%, 0); left: 50%; } .better-xcloud-stats-bar[data-transparent=true] { background: none; filter: drop-shadow(1px 0 0 #000) drop-shadow(-1px 0 0 #000) drop-shadow(0 1px 0 #000) drop-shadow(0 -1px 0 #000); } .better-xcloud-stats-bar label { margin: 0 8px 0 0; font-family: Bahnschrift, Arial, Helvetica, sans-serif; font-size: inherit; font-weight: bold; vertical-align: middle; } .better-xcloud-stats-bar span { min-width: 60px; display: inline-block; text-align: right; padding-right: 8px; margin-right: 8px; border-right: 2px solid #fff; vertical-align: middle; } .better-xcloud-stats-bar span[data-grade=good] { color: #6bffff; } .better-xcloud-stats-bar span[data-grade=ok] { color: #fff16b; } .better-xcloud-stats-bar span[data-grade=bad] { color: #ff5f5f; } .better-xcloud-stats-bar span:first-of-type { min-width: 30px; } .better-xcloud-stats-bar span:last-of-type { border: 0; margin-right: 0; } .better-xcloud-stats-settings { display: none; position: fixed; top: 50%; left: 50%; margin-right: -50%; transform: translate(-50%, -50%); width: 420px; padding: 20px; border-radius: 8px; z-index: 1000; background: #1a1b1e; color: #fff; font-weight: 400; font-size: 16px; font-family: "Segoe UI", Arial, Helvetica, sans-serif; box-shadow: 0 0 6px #000; user-select: none; } .better-xcloud-stats-settings *:focus { outline: none !important; } .better-xcloud-stats-settings > b { color: #fff; display: block; font-family: Bahnschrift, Arial, Helvetica, sans-serif; font-size: 26px; font-weight: 400; line-height: 32px; margin-bottom: 12px; } .better-xcloud-stats-settings > div { display: flex; margin-bottom: 8px; padding: 2px 4px; } .better-xcloud-stats-settings label { flex: 1; margin-bottom: 0; align-self: center; } .better-xcloud-stats-settings button { padding: 8px 32px; margin: 20px auto 0; border: none; border-radius: 4px; display: block; background-color: #2d3036; text-align: center; color: white; text-transform: uppercase; font-family: Bahnschrift, Arial, Helvetica, sans-serif; font-weight: 400; line-height: 18px; font-size: 14px; } @media (hover: hover) { .better-xcloud-stats-settings button:hover { background-color: #515863; } } .better-xcloud-stats-settings button:focus { background-color: #515863; } .better-xcloud-gone { display: none !important; } .better-xcloud-quick-settings-bar { display: none; user-select: none; -webkit-user-select: none; position: fixed; bottom: 0; left: 50%; transform: translate(-50%, 0); z-index: 9999; padding: 16px; width: 600px; background: #1a1b1e; color: #fff; border-radius: 8px 8px 0 0; font-weight: 400; font-size: 14px; font-family: Bahnschrift, Arial, Helvetica, sans-serif; text-align: center; box-shadow: 0px 0px 6px #000; opacity: 0.95; } .better-xcloud-quick-settings-bar *:focus { outline: none !important; } .better-xcloud-quick-settings-bar > div { flex: 1; } .better-xcloud-quick-settings-bar label { font-size: 16px; display: block; margin-bottom: 8px; } .better-xcloud-quick-settings-bar input { width: 22px; height: 22px; } .better-xcloud-quick-settings-bar button { border: none; width: 22px; height: 22px; margin: 0 4px; line-height: 22px; background-color: #515151; color: #fff; border-radius: 4px; } @media (hover: hover) { .better-xcloud-quick-settings-bar button:hover { background-color: #414141; color: white; } } .better-xcloud-quick-settings-bar button:active { background-color: #414141; color: white; } .better-xcloud-quick-settings-bar span { display: inline-block; width: 40px; font-weight: bold; font-family: Consolas, "Courier New", Courier, monospace; } .closeSetting1 { color: #0099CC; background: transparent; border: 2px solid #0099CC; border-radius: 6px; border: none; color: white; padding: 3px 13px; text-align: center; display: inline-block; font-size: 16px; margin: 4px 2px; -webkit-transition-duration: 0.4s; /* Safari */ transition-duration: 0.4s; cursor: pointer; text-decoration: none; text-transform: uppercase; } .closeSetting2 { background-color: white; color: black; border: 2px solid #008CBA; display: block; margin: 0 auto; margin-top: 5px; } .closeSetting2:hover { background-color: #008CBA; color: white; } .settingsBackgroud{ position: fixed; left: 0; top: 0; background: #0000; width: 100%; height: 100%; overflow: scroll; z-index:8888; } .settingsBox{ position: relative; background: wheat; width: fit-content; height: fit-content; border-radius: 5px; margin: 5% auto; padding: 10px; font-family: '微软雅黑'; line-height: 22px; top:5%; z-index:8889; } .settingsBoxInputRadio{ background-color: initial; cursor: pointer; appearance: auto; box-sizing: border-box; margin: 3px 3px 0px 5px; padding: initial; padding-top: initial; padding-right: initial; padding-bottom: initial; padding-left: initial; border: initial; -webkit-appearance: checkbox; accent-color: dodgerblue; } #StreamHud >div{ background-color:rgba(255,0,0,0)!important; } #StreamHud >button{ background-color:rgba(0,0,0,0)!important; } #StreamHud >button > div{ opacity:0.3!important; } #touchControllerEventArea { pointer-events: auto; position: fixed; bottom: 0; right: 0; width: 33%; height: 6vh; z-index: 5678; background-color: rgba(0, 0, 0, 0); } .better-xcloud-badges { bottom:-10px; padding-left: calc(env(safe-area-inset-left, 0px) + 48px); position: absolute; margin-left: 0px; user-select: none; -webkit-user-select: none; } .better-xcloud-badge { border: none; display: inline-block; line-height: 24px; color: #fff; font-family: Bahnschrift Semibold, Arial, Helvetica, sans-serif; font-size: 14px; font-weight: 400; margin: 0 8px 8px 0; box-shadow: 0px 0px 6px #000; border-radius: 4px; } .better-xcloud-badge-name { background-color: #2d3036; display: inline-block; padding: 2px 8px; border-radius: 4px 0 0 4px; text-transform: uppercase; } .better-xcloud-badge-value { background-color: grey; display: inline-block; padding: 2px 8px; border-radius: 0 4px 4px 0; } .better-xcloud-badge-battery[data-charging=true] span:first-of-type::after { content: ' ⚡️'; } div[class*=StreamMenu-module__menuContainer] { height:75%!important; } div[class*=NotFocusedDialog-module__container] { display:none } @keyframes blink { 20% {color: blueviolet; } 50% { color: blue; } 100% { color: green; } } .blink-text { font-size: 15px; font-weight: bold; animation: blink 3s infinite; float: right; cursor: pointer; display:none } .remote-play-button { background-color: transparent; border: none; color: white; font-weight: bold; line-height: 30px; border-radius: 4px; padding: 8px; } .remote-play-button:hover, .remote-play-button:focus { background-color: #515863; } .bx-dialog-overlay { position: fixed; inset: 0; z-index: var(--bx-dialog-overlay-z-index); background: black; opacity: 50%; } .bx-dialog { display: flex; flex-flow: column; max-height: 90vh; position: fixed; top: 50%; left: 50%; margin-right: -50%; transform: translate(-50%, -50%); max-width: 410px; width:95%; padding: 20px; border-radius: 8px; z-index: var(--bx-dialog-z-index); background: #1a1b1e; color: #fff; font-weight: 400; font-size: 16px; font-family: var(--bx-normal-font); box-shadow: 0 0 6px #000; user-select: none; -webkit-user-select: none; } .bx-dialog *:focus { outline: none !important; } .bx-dialog > b { color: #fff; display: block; font-family: var(--bx-title-font); font-size: 26px; font-weight: 400; line-height: 32px; margin-bottom: 12px; } .bx-dialog > div { overflow: auto; padding: 2px 0; } .bx-dialog > button { padding: 8px 32px; margin: 20px auto 0; border: none; border-radius: 4px; display: block; background-color: #2d3036; text-align: center; color: white; text-transform: uppercase; font-family: var(--bx-title-font); font-weight: 400; line-height: 18px; font-size: 14px; } .bx-gone { display: none !important; } .bx-remote-play-settings { margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid #2d2d2d; } .bx-remote-play-settings > div { display: flex; } .bx-remote-play-settings label { flex: 1; } .bx-remote-play-settings label p { margin: 4px 0 0; padding: 0; color: #888; font-size: 12px; } .bx-remote-play-settings input { display: block; margin: 0 auto; } .bx-remote-play-settings span { font-weight: bold; font-size: 18px; display: block; margin-bottom: 8px; text-align: center; } .bx-remote-play-device-name { font-size: 20px; font-weight: bold; display: inline-block; vertical-align: middle; } .bx-remote-play-console-type { font-size: 12px; background: #888; color: #fff; display: inline-block; border-radius: 14px; padding: 2px 10px; margin-left: 8px; vertical-align: middle; } .bx-remote-play-power-state { color: #888; font-size: 14px; } .bx-remote-play-power-state { color: #888; font-size: 14px; } .bx-primary-button { padding: 8px 32px; margin: 10px auto 0; border: none; border-radius: 4px; display: block; background-color: #044e2a; text-align: center; color: white; text-transform: uppercase; font-family: var(--bx-title-font); font-weight: 400; font-size: 14px; line-height: 24px; } @media (hover: hover) { .bx-primary-button:hover { background-color: #00753c; } } .bx-primary-button:focus { background-color: #00753c; } .bx-primary-button:active { background-color: #00753c; } .bx-primary-button[disabled] { background: #393939; color: #a2a2a2; } .bx-no-margin { margin: 0 !important; } .bx-remote-play-device-info { flex: 1; padding: 4px 0; } .bx-remote-play-device-wrapper { display: flex; margin-bottom: 8px; } .bx-remote-play-device-wrapper:not(:last-child) { margin-bottom: 14px; } `; if (NFTconfig['disableTouchControls'] == 1) { popCss += ` #MultiTouchSurface, #BabylonCanvasContainer-main { display: none !important; } `}; let xfbasicStyle = document.createElement('style'); xfbasicStyle.innerHTML = popCss; let docxf = document.head || document.documentElement; docxf.appendChild(xfbasicStyle); 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'); } } exitGame(); return _pushState.apply(this, arguments); } window.onpopstate = function (event) { if (event.state) { if (window.location.href.slice(-5) == '/play') { exitGame(); } } }; RTCPeerConnection.prototype.orgAddIceCandidate = RTCPeerConnection.prototype.addIceCandidate; RTCPeerConnection.prototype.addIceCandidate = function (...args) { const candidate = args[0].candidate; if (candidate && candidate.startsWith('a=candidate:1 ')) { STREAM_WEBRTC = this; StreamBadges.ipv6 = candidate.substring(20).includes(':'); } STREAM_WEBRTC = this; return this.orgAddIceCandidate.apply(this, args); } 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.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); }); } catch(e) {} STREAM_WEBRTC.getStats().then(stats => { const allVideoCodecs = {}; let videoCodecId; const allAudioCodecs = {}; let audioCodecId; 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; } } }); // 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, } } }); } function moveCodecToIndex(array, currentIndex, targetIndex, element) { array.splice(currentIndex, 1); array.splice(targetIndex, 0, element); } function customizeRtcCodecs() { const customCodecProfile = NFTconfig['video_quality']['default']; if (customCodecProfile === '默认') { return; } if (typeof RTCRtpTransceiver === 'undefined' || !('setCodecPreferences' in RTCRtpTransceiver.prototype)) { return false; } let codecProfilePrefix=""; let codecProfileLevelId = ""; let codecMimeType=""; const codecProfileMap = {"264": {"高": "4d","中": "42e","低": "420"}}; if (customCodecProfile.includes("264")) { const codecLevel = Object.keys(codecProfileMap["264"]).find(level => customCodecProfile.includes(level)); if (codecLevel) { codecProfilePrefix = codecProfileMap["264"][codecLevel]; codecProfileLevelId = `profile-level-id=${codecProfilePrefix}`; } }else{ codecMimeType="video/"+customCodecProfile; } RTCRtpTransceiver.prototype.originalSetCodecPreferences = RTCRtpTransceiver.prototype.setCodecPreferences; RTCRtpTransceiver.prototype.setCodecPreferences = function(codecs) { const customizedCodecs = codecs.slice(); let insertionIndex = 0; customizedCodecs.forEach((codec, index) => { if (codecProfileLevelId !== '' && codec.sdpFmtpLine && codec.sdpFmtpLine.includes(codecProfileLevelId)) { moveCodecToIndex(customizedCodecs, index, insertionIndex, codec); insertionIndex++; } else if (codec.mimeType === codecMimeType) { moveCodecToIndex(customizedCodecs, index, insertionIndex, codec); insertionIndex++; } }); 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('/launch/') && window.location.hash.startsWith('#remote-play'); if (IS_REMOTE_PLAYING) { window.BX_REMOTE_PLAY_CONFIG = REMOTE_PLAY_CONFIG; // 移除 /launch/... window.history.replaceState({origin: 'better-xcloud'}, '', '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'); //添加串流按钮 var targetElement = $("[title*='Account Settings']"); var newButton = $("