Xbox CLoud Gaming优化整合

脚本免费!谨防上当受骗!整合和修改现有脚本,优化项详见脚本说明。【若你有好的想法或者BUG可以进xbox云游戏QQ交流1群531602832,2群313340764,3群826510890反馈】

目前为 2023-12-03 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Xbox CLoud Gaming优化整合
  3. // @name:zh-CN Xbox CLoud Gaming优化整合
  4. // @namespace http://tampermonkey.net/xbox/nft
  5. // @version 3.9.2.5
  6. // @author 奈非天
  7. // @license MIT
  8. // @match https://www.xbox.com/*/play*
  9. // @run-at document-start
  10. // @grant unsafeWindow
  11. // @require https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.4.1/jquery.min.js
  12. // @original-script https://gf.qytechs.cn/zh-CN/scripts/455741-xbox-cloud-gaming%E4%BC%98%E5%8C%96%E6%95%B4%E5%90%88
  13. // @description:zh-cn 脚本免费!谨防上当受骗!整合和修改现有脚本,优化项详见脚本说明。【若你有好的想法或者BUG可以进xbox云游戏QQ交流1群531602832,2群313340764,3群826510890反馈】
  14. // @description 脚本免费!谨防上当受骗!整合和修改现有脚本,优化项详见脚本说明。【若你有好的想法或者BUG可以进xbox云游戏QQ交流1群531602832,2群313340764,3群826510890反馈】
  15. // ==/UserScript==
  16. (function () {
  17. 'use strict';
  18. // Your code here...
  19.  
  20.  
  21. let nftxboxversion = 'v3.9.2.5';
  22.  
  23. let naifeitian = {
  24. isType(obj) {
  25. return Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1').toLowerCase();
  26. },
  27. getValue(key) {
  28. try {
  29. return JSON.parse(localStorage.getItem(key));
  30. } catch (e) {
  31. return localStorage.getItem(key);
  32. }
  33. },
  34.  
  35. setValue(key, value) {
  36. if (this.isType(value) === 'object' || this.isType(value) === 'array' || this.isType(value) === 'boolean') {
  37. return localStorage.setItem(key, JSON.stringify(value));
  38. }
  39. return localStorage.setItem(key, value);
  40. },
  41. isValidIP(ip) {
  42. 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])$/
  43. return reg.test(ip);
  44. },
  45. isNumber(val) {
  46. return !isNaN(parseFloat(val)) && isFinite(val);
  47. },
  48. toElement(key, onChange) {
  49. const CE = createElement;
  50. const setting = key;
  51. const currentValue = key['default'] == undefined ? key : key['default'];
  52.  
  53. let $control;
  54. if (setting['options'] != undefined) {
  55.  
  56. $control = CE('select', { id: 'xcloud_setting_' + key['name'] });
  57.  
  58. for (let value in setting.options) {
  59. const label = setting.options[value];
  60.  
  61. const $option = CE('option', { value: value }, label);
  62. $control.appendChild($option);
  63. }
  64.  
  65. $control.value = currentValue;
  66. $control.addEventListener('change', e => {
  67. key['default'] = e.target.value;
  68.  
  69. this.setValue(key['name'], key);
  70. onChange && onChange(e);
  71. });
  72.  
  73. } else if (typeof setting.default === 'number') {
  74. $control = CE('input', { 'type': 'number', 'min': setting.min, 'max': setting.max });
  75.  
  76. $control.value = currentValue;
  77. $control.addEventListener('change', e => {
  78. let value = Math.max(setting.min, Math.min(setting.max, parseInt(e.target.value)));
  79. e.target.value = value;
  80.  
  81. key['default'] = e.target.value
  82. this.setValue(key['name'], key);
  83. onChange && onChange(e);
  84. });
  85. } else {
  86. $control = CE('input', { 'type': 'checkbox' });
  87. $control.checked = currentValue;
  88.  
  89. $control.addEventListener('change', e => {
  90. key['default'] = e.target.checked;
  91. NFTconfig[key['name'].slice(0,-2)]['default']=e.target.checked;
  92. this.setValue(key['name'], key);
  93. if(key['name']=='STATS_SLIDE_OPENGM' && e.target.checked){
  94. if(this.getValue('STATS_SHOW_WHEN_PLAYINGGM')['default']){
  95. $('#xcloud_setting_STATS_SHOW_WHEN_PLAYINGGM').click();
  96. }
  97. }else if(key['name']=='STATS_SHOW_WHEN_PLAYINGGM' && e.target.checked){
  98. if(this.getValue('STATS_SLIDE_OPENGM')['default']){
  99. $('#xcloud_setting_STATS_SLIDE_OPENGM').click();
  100. }
  101. }
  102. onChange && onChange(e);
  103. });
  104. }
  105.  
  106. $control.id = `xcloud_setting_${key.name}`;
  107. return $control;
  108. },
  109. isSafari() {
  110. let userAgent = userAgentOriginal.toLowerCase();
  111. if (userAgent.indexOf('safari') !== -1 && userAgent.indexOf('chrome') === -1) {
  112. return true;
  113. } else {
  114. return false;
  115. }
  116. },
  117. getGM(defaultValue, n) {
  118. let newval = this.getValue(n) == null ? defaultValue : this.getValue(n);
  119. if(newval.options!=undefined){
  120. newval.options=defaultValue.options;
  121. }
  122. naifeitian.setValue(n, newval);
  123. return newval;
  124. },
  125. showSetting(){
  126. $('#settingsBackgroud').css('display', '');
  127. $('body').css('overflow','hidden');
  128. },
  129. hideSetting(){
  130. $('#settingsBackgroud').css('display', 'none');
  131. $('body').css('overflow','visible');
  132. }
  133.  
  134. }
  135. //★★ 1=开 0=关 ★★//
  136. let default_language_list = { '智能简繁': 'Auto', '简体': 'zh-CN', '繁体': 'zh-TW' }
  137. let NFTconfig =
  138. {
  139. no_need_VPN_play: 1,
  140. regionsList: {
  141. '韩服': '168.126.63.1',
  142. '美服': '4.2.2.2',
  143. '日服': '210.131.113.123'
  144. },
  145. fakeIp: '',
  146. chooseLanguage: 1,
  147. IfErrUsedefaultGameLanguage: 'zh-CN',
  148. CustomUA:{
  149. default:'Edge on Windows',
  150. options:{
  151. 'Edge on Windows':'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',
  152. 'Safari on macOS':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
  153. 'Samsung Smart Tv':'Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) 94.0.4606.31/7.0 TV Safari/537.36',
  154. 'Firefox on Windows':'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101213 Firefox/4.0b8pre',
  155. '自定义':''
  156. },
  157. },
  158. CustomUAUser:'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',
  159. high_bitrate: 1,
  160. autoOpenOC: 0,
  161. disableCheckNetwork: 1,
  162. slideToHide: 0,
  163. IPv6: 0,
  164. autoFullScreen: 0,
  165. blockXcloudServer: 0,
  166. blockXcloudServerList: ['AustraliaEast', 'AustraliaSouthEast', 'BrazilSouth', 'EastUS', 'EastUS2', 'JapanEast', 'KoreaCentral', 'NorthCentralUs', 'SouthCentralUS', 'UKSouth', 'WestEurope', 'WestUS', 'WestUS2'],
  167. defaultXcloudServer: 'KoreaCentral',
  168. video_stretch: {
  169. default: 'none',
  170. options: {
  171. none: '无',
  172. fill: '填充',
  173. setting: '微调'
  174. },
  175. name: 'video_stretchGM'
  176. },
  177.  
  178. video_stretch_x_y: {
  179. x: 0,
  180. y: 0,
  181. name: 'video_stretch_x_yGM'
  182. },
  183.  
  184. noPopSetting: 0,
  185. disableTouchControls: 0,
  186. canShowOC: false,
  187. autoShowTouch: true,
  188. STATS_SHOW_WHEN_PLAYING: {
  189. default: false,
  190. name: 'STATS_SHOW_WHEN_PLAYINGGM'
  191. },
  192.  
  193. STATS_POSITION: {
  194. default: 'top-left',
  195. options: {
  196. 'top-left': '上左',
  197. 'top-center': '上中',
  198. 'top-right': '上右'
  199. },
  200.  
  201. name: 'STATS_POSITIONGM'
  202. },
  203.  
  204. STATS_TRANSPARENT: {
  205. default: false,
  206. name: 'STATS_TRANSPARENTGM'
  207. },
  208.  
  209. STATS_OPACITY: {
  210. default: 80,
  211. min: 10,
  212. max: 100,
  213. name: 'STATS_OPACITYGM'
  214. },
  215.  
  216. STATS_TEXT_SIZE: {
  217. default: '0.9rem',
  218. options: {
  219. '0.9rem': '小',
  220. '1.0rem': '中',
  221. '1.1rem': '大'
  222. },
  223.  
  224. name: 'STATS_TEXT_SIZEGM'
  225. },
  226.  
  227. STATS_CONDITIONAL_FORMATTING: {
  228. default: false,
  229. name: 'STATS_CONDITIONAL_FORMATTINGGM'
  230. },
  231. STATS_SLIDE_OPEN:{
  232. default: false,
  233. name: 'STATS_SLIDE_OPENGM'
  234. },
  235.  
  236. video_quality:{
  237. default:'默认',
  238. options: [
  239. '默认',
  240. '低',
  241. '中',
  242. '高'
  243. ]
  244. },
  245. VIDEO_CLARITY: {
  246. default: 0,
  247. min: 0,
  248. max: 3,
  249. name: 'VIDEO_CLARITYGM'
  250. },
  251.  
  252. VIDEO_CONTRAST: {
  253. default: 100,
  254. min: 0,
  255. max: 150,
  256. name: 'VIDEO_CONTRASTGM'
  257. },
  258.  
  259. VIDEO_SATURATION: {
  260. default: 100,
  261. min: 0,
  262. max: 150,
  263. name: 'VIDEO_SATURATIONGM'
  264. },
  265.  
  266. VIDEO_BRIGHTNESS: {
  267. default: 100,
  268. min: 0,
  269. max: 150,
  270. name: 'VIDEO_BRIGHTNESSGM'
  271. },
  272. antiKick: 0,
  273. customfakeIp: 0,
  274. customfakeIp: '',
  275. xcloud_game_language:default_language_list['简体']
  276. }
  277. NFTconfig['fakeIp']=NFTconfig['regionsList']['美服'];
  278. const integratekeys = Object.keys(NFTconfig);
  279.  
  280. integratekeys.forEach(key => {
  281. NFTconfig[key] = naifeitian.getGM(NFTconfig[key], key + 'GM');
  282. });
  283.  
  284. const originFetch = fetch;
  285. let regionsMenuItemList = [];
  286. let languageMenuItemList = [];
  287.  
  288. let letmeOb = true;
  289.  
  290. let STREAM_WEBRTC;
  291. const ICON_STREAM_STATS = '<path d="M12.005 5C9.184 5 6.749 6.416 5.009 7.903c-.87.743-1.571 1.51-2.074 2.18-.251.335-.452.644-.605.934-.434.733-.389 1.314-.004 1.98a6.98 6.98 0 0 0 .609.949 13.62 13.62 0 0 0 2.076 2.182C6.753 17.606 9.188 19 12.005 19s5.252-1.394 6.994-2.873a13.62 13.62 0 0 0 2.076-2.182 6.98 6.98 0 0 0 .609-.949c.425-.737.364-1.343-.004-1.98-.154-.29-.354-.599-.605-.934-.503-.669-1.204-1.436-2.074-2.18C17.261 6.416 14.826 5 12.005 5zm0 2c2.135 0 4.189 1.135 5.697 2.424.754.644 1.368 1.32 1.773 1.859.203.27.354.509.351.733s-.151.462-.353.732c-.404.541-1.016 1.214-1.77 1.854C16.198 15.881 14.145 17 12.005 17s-4.193-1.12-5.699-2.398a11.8 11.8 0 0 1-1.77-1.854c-.202-.27-.351-.508-.353-.732s.149-.463.351-.733c.406-.54 1.019-1.215 1.773-1.859C7.816 8.135 9.87 7 12.005 7zm.025 1.975c-1.645 0-3 1.355-3 3s1.355 3 3 3 3-1.355 3-3-1.355-3-3-3zm0 2c.564 0 1 .436 1 1s-.436 1-1 1-1-.436-1-1 .436-1 1-1z"/>';
  292. const ICON_VIDEO_SETTINGS = '<path d="M16 9.144A6.89 6.89 0 0 0 9.144 16 6.89 6.89 0 0 0 16 22.856 6.89 6.89 0 0 0 22.856 16 6.9 6.9 0 0 0 16 9.144zm0 11.427c-2.507 0-4.571-2.064-4.571-4.571s2.064-4.571 4.571-4.571 4.571 2.064 4.571 4.571-2.064 4.571-4.571 4.571zm15.704-7.541c-.065-.326-.267-.607-.556-.771l-4.26-2.428-.017-4.802c-.001-.335-.15-.652-.405-.868-1.546-1.307-3.325-2.309-5.245-2.953-.306-.103-.641-.073-.923.085L16 3.694l-4.302-2.407c-.282-.158-.618-.189-.924-.086a16.02 16.02 0 0 0-5.239 2.964 1.14 1.14 0 0 0-.403.867L5.109 9.84.848 12.268a1.14 1.14 0 0 0-.555.771 15.22 15.22 0 0 0 0 5.936c.064.326.267.607.555.771l4.261 2.428.017 4.802c.001.335.149.652.403.868 1.546 1.307 3.326 2.309 5.245 2.953.306.103.641.073.923-.085L16 28.306l4.302 2.407a1.13 1.13 0 0 0 .558.143 1.18 1.18 0 0 0 .367-.059c1.917-.648 3.695-1.652 5.239-2.962.255-.216.402-.532.405-.866l.021-4.807 4.261-2.428a1.14 1.14 0 0 0 .555-.771 15.21 15.21 0 0 0-.003-5.931zm-2.143 4.987l-4.082 2.321a1.15 1.15 0 0 0-.429.429l-.258.438a1.13 1.13 0 0 0-.174.601l-.022 4.606a13.71 13.71 0 0 1-3.623 2.043l-4.117-2.295a1.15 1.15 0 0 0-.559-.143h-.546c-.205-.005-.407.045-.586.143l-4.119 2.3a13.74 13.74 0 0 1-3.634-2.033l-.016-4.599a1.14 1.14 0 0 0-.174-.603l-.257-.437c-.102-.182-.249-.333-.429-.437l-4.085-2.328a12.92 12.92 0 0 1 0-4.036l4.074-2.325a1.15 1.15 0 0 0 .429-.429l.258-.438a1.14 1.14 0 0 0 .175-.601l.021-4.606a13.7 13.7 0 0 1 3.625-2.043l4.11 2.295a1.14 1.14 0 0 0 .585.143h.52c.205.005.407-.045.586-.143l4.119-2.3a13.74 13.74 0 0 1 3.634 2.033l.016 4.599a1.14 1.14 0 0 0 .174.603l.257.437c.102.182.249.333.429.438l4.085 2.327a12.88 12.88 0 0 1 .007 4.041h.007z" fill-rule="nonzero"/>';
  293. // Quickly create a tree of elements without having to use innerHTML
  294. function createElement(elmName, props = {}) {
  295. let $elm;
  296. const hasNs = 'xmlns' in props;
  297.  
  298. if (hasNs) {
  299. $elm = document.createElementNS(props.xmlns, elmName);
  300. } else {
  301. $elm = document.createElement(elmName);
  302. }
  303.  
  304. for (let key in props) {
  305. if (key === 'xmlns') {
  306. continue;
  307. }
  308.  
  309. if (!props.hasOwnProperty(key) || $elm.hasOwnProperty(key)) {
  310. continue;
  311. }
  312.  
  313. if (hasNs) {
  314. $elm.setAttributeNS(null, key, props[key]);
  315. } else {
  316. $elm.setAttribute(key, props[key]);
  317. }
  318. }
  319.  
  320. for (let i = 2, size = arguments.length; i < size; i++) {
  321. const arg = arguments[i];
  322. const argType = typeof arg;
  323.  
  324. if (argType === 'string' || argType === 'number') {
  325. $elm.textContent = arg;
  326. } else if (arg) {
  327. $elm.appendChild(arg);
  328. }
  329. }
  330.  
  331. return $elm;
  332. }
  333.  
  334. function setMachineFullScreen() {
  335. try {
  336. let element = document.documentElement;
  337. if (element.requestFullscreen) {
  338. element.requestFullscreen();
  339. } else if (element.mozRequestFullScreen) {
  340. element.mozRequestFullScreen();
  341. } else if (element.msRequestFullscreen) {
  342. element.msRequestFullscreen();
  343. } else if (element.webkitRequestFullscreen) {
  344. element.webkitRequestFullScreen();
  345. }
  346. screen?.orientation?.lock("landscape");
  347. } catch (e) {
  348. }
  349. }
  350.  
  351. function exitMachineFullscreen() {
  352. try {
  353. screen?.orientation?.unlock();
  354. if (document.exitFullScreen) {
  355. document.exitFullScreen();
  356. } else if (document.mozCancelFullScreen) {
  357. document.mozCancelFullScreen();
  358. } else if (document.webkitExitFullscreen) {
  359. document.webkitExitFullscreen();
  360. } else if (element.msExitFullscreen) {
  361. element.msExitFullscreen();
  362. }
  363. } catch (e) {
  364. }
  365. }
  366. function exitGame() {
  367. document.documentElement.style.overflowY = "";
  368. letmeOb = true;
  369. transitionComplete = true;
  370. StreamStats.stop();
  371. bindmslogoevent();
  372. const $quickBar = document.querySelector('.better-xcloud-quick-settings-bar');
  373. if ($quickBar) {
  374. $quickBar.style.display = 'none';
  375. }
  376. if (NFTconfig['autoFullScreen'] == 1) {
  377. exitMachineFullscreen();
  378. }
  379. if (NFTconfig['noPopSetting'] == 0) {
  380. $('#popSetting').css('display', 'block');
  381. }
  382. }
  383. class StreamBadges {
  384. static get BADGE_PLAYTIME() { return '游玩时间'; };
  385. static get BADGE_BATTERY() { return '电量'; };
  386. static get BADGE_IN() { return '下载'; };
  387. static get BADGE_OUT() { return '上传'; };
  388.  
  389. static get BADGE_SERVER() { return '服务器'; };
  390. static get BADGE_VIDEO() { return '解码'; };
  391. static get BADGE_AUDIO() { return '音频'; };
  392.  
  393. static get BADGE_BREAK() { return 'break'; };
  394.  
  395. static ipv6 = false;
  396. static resolution = null;
  397. static video = null;
  398. static audio = null;
  399. static fps = 0;
  400. static region = '';
  401.  
  402. static startBatteryLevel = 100;
  403. static startTimestamp = 0;
  404.  
  405. static #cachedDoms = {};
  406.  
  407. static #interval;
  408. static get #REFRESH_INTERVAL() { return 3000; };
  409.  
  410. static #renderBadge(name, value, color) {
  411. const CE = createElement;
  412.  
  413. if (name === StreamBadges.BADGE_BREAK) {
  414. return CE('div', {'style': 'display: block'});
  415. }
  416.  
  417. let $badge;
  418. if (StreamBadges.#cachedDoms[name]) {
  419. $badge = StreamBadges.#cachedDoms[name];
  420. $badge.lastElementChild.textContent = value;
  421. return $badge;
  422. }
  423.  
  424. $badge = CE('div', {'class': 'better-xcloud-badge'},
  425. CE('span', {'class': 'better-xcloud-badge-name'}, name),
  426. CE('span', {'class': 'better-xcloud-badge-value', 'style': `background-color: ${color}`}, value));
  427.  
  428. if (name === StreamBadges.BADGE_BATTERY) {
  429. $badge.classList.add('better-xcloud-badge-battery');
  430. }
  431.  
  432. StreamBadges.#cachedDoms[name] = $badge;
  433. return $badge;
  434. }
  435.  
  436. static async #updateBadges(forceUpdate) {
  437. if (!forceUpdate && !document.querySelector('.better-xcloud-badges')) {
  438. StreamBadges.#stop();
  439. return;
  440. }
  441.  
  442. // 游玩时间
  443. let now = +new Date;
  444. const diffSeconds = Math.ceil((now - StreamBadges.startTimestamp) / 1000);
  445. const playtime = StreamBadges.#secondsToHm(diffSeconds);
  446.  
  447. // 电量
  448. let batteryLevel = '100%';
  449. let batteryLevelInt = 100;
  450. let isCharging = false;
  451. if (navigator.getBattery) {
  452. try {
  453. const bm = await navigator.getBattery();
  454. isCharging = bm.charging;
  455. batteryLevelInt = Math.round(bm.level * 100);
  456. batteryLevel = `${batteryLevelInt}%`;
  457.  
  458. if (batteryLevelInt != StreamBadges.startBatteryLevel) {
  459. const diffLevel = Math.round(batteryLevelInt - StreamBadges.startBatteryLevel);
  460. const sign = diffLevel > 0 ? '+' : '';
  461. batteryLevel += ` (${sign}${diffLevel}%)`;
  462. }
  463. } catch(e) {}
  464. }
  465.  
  466. const stats = await STREAM_WEBRTC.getStats();
  467. let totalIn = 0;
  468. let totalOut = 0;
  469. stats.forEach(stat => {
  470. if (stat.type === 'candidate-pair' && stat.state == 'succeeded') {
  471. totalIn += stat.bytesReceived;
  472. totalOut += stat.bytesSent;
  473. }
  474. });
  475.  
  476. const badges = {
  477. [StreamBadges.BADGE_IN]: totalIn ? StreamBadges.#humanFileSize(totalIn) : null,
  478. [StreamBadges.BADGE_OUT]: totalOut ? StreamBadges.#humanFileSize(totalOut) : null,
  479. [StreamBadges.BADGE_PLAYTIME]: playtime,
  480. [StreamBadges.BADGE_BATTERY]: batteryLevel,
  481. };
  482.  
  483. for (let name in badges) {
  484. const value = badges[name];
  485. if (value === null) {
  486. continue;
  487. }
  488.  
  489. const $elm = StreamBadges.#cachedDoms[name];
  490. $elm && ($elm.lastElementChild.textContent = value);
  491.  
  492. if (name === StreamBadges.BADGE_BATTERY) {
  493. // Show charging status
  494. $elm.setAttribute('data-charging', isCharging);
  495.  
  496. if (StreamBadges.startBatteryLevel === 100 && batteryLevelInt === 100) {
  497. $elm.style.display = 'none';
  498. } else {
  499. $elm.style = '';
  500. }
  501. }
  502. }
  503. }
  504.  
  505. static #stop() {
  506. StreamBadges.#interval && clearInterval(StreamBadges.#interval);
  507. StreamBadges.#interval = null;
  508. }
  509.  
  510. static #secondsToHm(seconds) {
  511. const h = Math.floor(seconds / 3600);
  512. const m = Math.floor(seconds % 3600 / 60) + 1;
  513.  
  514. const hDisplay = h > 0 ? `${h}小时`: '';
  515. const mDisplay = m > 0 ? `${m}分钟`: '';
  516. return hDisplay + mDisplay;
  517. }
  518.  
  519. // https://stackoverflow.com/a/20732091
  520. static #humanFileSize(size) {
  521. let i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
  522. return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
  523. }
  524.  
  525. static async render() {
  526. // Video
  527. let video = '';
  528. if (StreamBadges.resolution) {
  529. video = `${StreamBadges.resolution.height}p`;
  530. }
  531.  
  532. if (StreamBadges.video) {
  533. video && (video += '/');
  534. video += StreamBadges.video.codec;
  535. if (StreamBadges.video.profile) {
  536. let profile = StreamBadges.video.profile;
  537. profile = profile.startsWith('4d') ? '高' : (profile.startsWith('42e') ? '中' : '低');
  538. video += ` (${profile})`;
  539. }
  540. }
  541.  
  542. // 音频
  543. let audio;
  544. if (StreamBadges.audio) {
  545. audio = StreamBadges.audio.codec;
  546. const bitrate = StreamBadges.audio.bitrate / 1000;
  547. audio += ` (${bitrate} kHz)`;
  548. }
  549.  
  550. // 电量
  551. let batteryLevel = '';
  552. if (navigator.getBattery) {
  553. batteryLevel = '100%';
  554. }
  555.  
  556. // Server + Region
  557. let server = StreamBadges.region;
  558. server += '@' + (StreamBadges.ipv6 ? 'IPv6' : 'IPv4');
  559.  
  560. const BADGES = [
  561. [StreamBadges.BADGE_PLAYTIME, '1m', '#ff004d'],
  562. [StreamBadges.BADGE_BATTERY, batteryLevel, '#00b543'],
  563. [StreamBadges.BADGE_IN, StreamBadges.#humanFileSize(0), '#29adff'],
  564. [StreamBadges.BADGE_OUT, StreamBadges.#humanFileSize(0), '#ff77a8'],
  565. [StreamBadges.BADGE_BREAK],
  566. [StreamBadges.BADGE_SERVER, server, '#ff6c24'],
  567. video ? [StreamBadges.BADGE_VIDEO, video, '#742f29'] : null,
  568. audio ? [StreamBadges.BADGE_AUDIO, audio, '#5f574f'] : null,
  569. ];
  570.  
  571. const $wrapper = createElement('div', {'class': 'better-xcloud-badges'});
  572. BADGES.forEach(item => item && $wrapper.appendChild(StreamBadges.#renderBadge(...item)));
  573.  
  574. await StreamBadges.#updateBadges(true);
  575. StreamBadges.#stop();
  576. StreamBadges.#interval = setInterval(StreamBadges.#updateBadges, StreamBadges.#REFRESH_INTERVAL);
  577.  
  578. return $wrapper;
  579. }
  580. }
  581. class StreamStats {
  582. static #interval;
  583. static #updateInterval = 1000;
  584.  
  585. static #$container;
  586. static #$fps;
  587. static #$rtt;
  588. static #$dt;
  589. static #$pl;
  590. static #$fl;
  591. static #$br;
  592.  
  593. static #$settings;
  594.  
  595. static #lastStat;
  596.  
  597. static start() {
  598. clearInterval(StreamStats.#interval);
  599.  
  600. StreamStats.#$container.classList.remove('better-xcloud-gone');
  601. StreamStats.#interval = setInterval(StreamStats.update, StreamStats.#updateInterval);
  602. }
  603.  
  604. static stop() {
  605. clearInterval(StreamStats.#interval);
  606.  
  607. StreamStats.#$container.classList.add('better-xcloud-gone');
  608. StreamStats.#interval = null;
  609. StreamStats.#lastStat = null;
  610. }
  611.  
  612. static toggle() {
  613. StreamStats.#isHidden() ? StreamStats.start() : StreamStats.stop();
  614. }
  615.  
  616. static #isHidden = () => StreamStats.#$container.classList.contains('better-xcloud-gone');
  617.  
  618. static update() {
  619. if (StreamStats.#isHidden() || !STREAM_WEBRTC) {
  620. StreamStats.stop();
  621. return;
  622. }
  623.  
  624. STREAM_WEBRTC.getStats().then(stats => {
  625. stats.forEach(stat => {
  626. let grade = '';
  627. if (stat.type === 'inbound-rtp' && stat.kind === 'video') {
  628. // FPS
  629. StreamStats.#$fps.textContent = stat.framesPerSecond || 0;
  630.  
  631. // Packets Lost
  632. const packetsLost = stat.packetsLost;
  633. if (packetsLost != undefined) {
  634. const packetsReceived = stat.packetsReceived;
  635. const packetsLostPercentage = (packetsLost * 100 / ((packetsLost + packetsReceived) || 1)).toFixed(2);
  636. StreamStats.#$pl.textContent = `${packetsLost} (${packetsLostPercentage}%)`;
  637. }else{
  638. StreamStats.#$pl.textContent = `-1 (-1%)`;
  639. }
  640.  
  641. // Frames Dropped
  642. const framesDropped = stat.framesDropped;
  643. if (framesDropped != undefined) {
  644. const framesReceived = stat.framesReceived;
  645. const framesDroppedPercentage = (framesDropped * 100 / ((framesDropped + framesReceived) || 1)).toFixed(2);
  646. StreamStats.#$fl.textContent = `${framesDropped} (${framesDroppedPercentage}%)`;
  647. }else{
  648. StreamStats.#$fl.textContent = `-1 (-1%)`;
  649. }
  650. if (StreamStats.#lastStat) {
  651. const lastStat = StreamStats.#lastStat;
  652. // Bitrate
  653. const timeDiff = stat.timestamp - lastStat.timestamp;
  654. const bitrate = 8 * (stat.bytesReceived - lastStat.bytesReceived) / timeDiff / 1000;
  655. StreamStats.#$br.textContent = `${bitrate.toFixed(2)} Mbps`;
  656.  
  657. // Decode time
  658. const totalDecodeTimeDiff = stat.totalDecodeTime - lastStat.totalDecodeTime;
  659. const framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded;
  660. const currentDecodeTime = totalDecodeTimeDiff / framesDecodedDiff * 1000;
  661. StreamStats.#$dt.textContent = `${currentDecodeTime.toFixed(2)}ms`;
  662.  
  663. if (NFTconfig['STATS_CONDITIONAL_FORMATTING']['default']) {
  664. grade = (currentDecodeTime > 12) ? 'bad' : (currentDecodeTime > 9) ? 'ok' : (currentDecodeTime > 6) ? 'good' : '';
  665. }
  666. StreamStats.#$dt.setAttribute('data-grade', grade);
  667. }
  668.  
  669. StreamStats.#lastStat = stat;
  670. } else if (stat.type === 'candidate-pair' && stat.state === 'succeeded') {
  671. // Round Trip Time
  672. const roundTripTime = typeof stat.currentRoundTripTime !== 'undefined' ? stat.currentRoundTripTime * 1000 : '???';
  673. StreamStats.#$rtt.textContent = `${roundTripTime}ms`;
  674.  
  675. if (NFTconfig['STATS_CONDITIONAL_FORMATTING']['default']) {
  676. grade = (roundTripTime > 100) ? 'bad' : (roundTripTime > 75) ? 'ok' : (roundTripTime > 40) ? 'good' : '';
  677. }
  678. StreamStats.#$rtt.setAttribute('data-grade', grade);
  679. }
  680. });
  681. });
  682. }
  683.  
  684. static #refreshStyles() {
  685. const PREF_POSITION = NFTconfig['STATS_POSITION']['default'];
  686. const PREF_TRANSPARENT = NFTconfig['STATS_TRANSPARENT']['default'];
  687. const PREF_OPACITY = NFTconfig['STATS_OPACITY']['default'];
  688. const PREF_TEXT_SIZE = NFTconfig['STATS_TEXT_SIZE']['default'];
  689.  
  690. StreamStats.#$container.setAttribute('data-position', PREF_POSITION);
  691. StreamStats.#$container.setAttribute('data-transparent', PREF_TRANSPARENT);
  692. StreamStats.#$container.style.opacity = PREF_OPACITY + '%';
  693. StreamStats.#$container.style.fontSize = PREF_TEXT_SIZE;
  694. }
  695.  
  696. static hideSettingsUi() {
  697. StreamStats.#$settings.style.display = 'none';
  698. }
  699.  
  700. static #toggleSettingsUi() {
  701. const display = StreamStats.#$settings.style.display;
  702. StreamStats.#$settings.style.display = display === 'block' ? 'none' : 'block';
  703. }
  704.  
  705. static render() {
  706. if (StreamStats.#$container) {
  707. return;
  708. }
  709.  
  710. const CE = createElement;
  711. StreamStats.#$container = CE('div', { 'class': 'better-xcloud-stats-bar better-xcloud-gone' },
  712. CE('label', {}, '帧率'),
  713. StreamStats.#$fps = CE('span', {}, 0),
  714. CE('label', {}, '延迟'),
  715. StreamStats.#$rtt = CE('span', {}, '0ms'),
  716. CE('label', {}, '解码'),
  717. StreamStats.#$dt = CE('span', {}, '0ms'),
  718. CE('label', {}, '码率'),
  719. StreamStats.#$br = CE('span', {}, '0 Mbps'),
  720. CE('label', {}, '丢包'),
  721. StreamStats.#$pl = CE('span', {}, '0 (0.00%)'),
  722. CE('label', {}, '丢帧'),
  723. StreamStats.#$fl = CE('span', {}, '0 (0.00%)'));
  724.  
  725. let clickTimeout;
  726. StreamStats.#$container.addEventListener('mousedown', e => {
  727. clearTimeout(clickTimeout);
  728. if (clickTimeout) {
  729. // Double-clicked
  730. clickTimeout = null;
  731. StreamStats.#toggleSettingsUi();
  732. return;
  733. }
  734.  
  735. clickTimeout = setTimeout(() => {
  736. clickTimeout = null;
  737. }, 400);
  738. });
  739.  
  740. document.documentElement.appendChild(StreamStats.#$container);
  741.  
  742. const refreshFunc = e => {
  743. StreamStats.#refreshStyles()
  744. };
  745. const $position = naifeitian.toElement(NFTconfig['STATS_POSITION'], refreshFunc);
  746.  
  747. let $close;
  748. const $showStartup = naifeitian.toElement(NFTconfig['STATS_SHOW_WHEN_PLAYING'], refreshFunc);
  749. const $transparent = naifeitian.toElement(NFTconfig['STATS_TRANSPARENT'], refreshFunc);
  750. const $formatting = naifeitian.toElement(NFTconfig['STATS_CONDITIONAL_FORMATTING'], refreshFunc);
  751. const $opacity = naifeitian.toElement(NFTconfig['STATS_OPACITY'], refreshFunc);
  752. const $textSize = naifeitian.toElement(NFTconfig['STATS_TEXT_SIZE'], refreshFunc);
  753. const $slideopen = naifeitian.toElement(NFTconfig['STATS_SLIDE_OPEN'], refreshFunc);
  754. StreamStats.#$settings = CE('div', { 'class': 'better-xcloud-stats-settings' },
  755. CE('b', {}, '状态条设置'),
  756. CE('div', {},
  757. CE('label', { 'for': `xcloud_setting_NFTconfig['STATS_SHOW_WHEN_PLAYING']` }, '游戏启动时显示状态条'),
  758. $showStartup
  759. ),
  760. CE('div', {},
  761. CE('label', {}, '位置'),
  762. $position
  763. ),
  764. CE('div', {},
  765. CE('label', {}, '字体大小'),
  766. $textSize
  767. ),
  768. CE('div', {},
  769. CE('label', { 'for': `xcloud_setting_STATS_OPACITY` }, '透明度 (10-100%)'),
  770. $opacity
  771. ),
  772. CE('div', {},
  773. CE('label', { 'for': `xcloud_setting_STATS_TRANSPARENT` }, '背景透明'),
  774. $transparent
  775. ),
  776. CE('div', {},
  777. CE('label', { 'for': `xcloud_setting_STATS_CONDITIONAL_FORMATTING` }, '数值颜色'),
  778. $formatting
  779. ),
  780. CE('div', {},
  781. CE('label', { 'for': `xcloud_setting_STATS_SLIDE_OPEN` }, '悬浮窗展开时打开'),
  782. $slideopen
  783. ),
  784.  
  785.  
  786.  
  787. $close = CE('button', {}, '关闭'));
  788.  
  789. $close.addEventListener('click', e => StreamStats.hideSettingsUi());
  790. document.documentElement.appendChild(StreamStats.#$settings);
  791.  
  792. StreamStats.#refreshStyles();
  793. }
  794. }
  795. function numberPicker(key, suffix = '', disabled = false) {
  796. const setting = key.name;
  797. let value = key.default;
  798. let $text, $decBtn, $incBtn;
  799.  
  800. const MIN = key.min;
  801. const MAX = key.max;
  802.  
  803. const CE = createElement;
  804. const $wrapper = CE('div', {},
  805. $decBtn = CE('button', { 'data-type': 'dec' }, '-'),
  806. $text = CE('span', {}, value + suffix),
  807. $incBtn = CE('button', { 'data-type': 'inc' }, '+'),
  808. );
  809.  
  810. if (disabled) {
  811. $incBtn.disabled = true;
  812. $incBtn.classList.add('better-xcloud-hidden');
  813.  
  814. $decBtn.disabled = true;
  815. $decBtn.classList.add('better-xcloud-hidden');
  816. return $wrapper;
  817. }
  818.  
  819. let interval;
  820. let isHolding = false;
  821.  
  822. const onClick = e => {
  823. if (isHolding) {
  824. e.preventDefault();
  825. isHolding = false;
  826.  
  827. return;
  828. }
  829.  
  830. const btnType = e.target.getAttribute('data-type');
  831. if (btnType === 'dec') {
  832. value = (value <= MIN) ? MIN : value - 1;
  833. } else {
  834. value = (value >= MAX) ? MAX : value + 1;
  835. }
  836.  
  837. $text.textContent = value + suffix;
  838.  
  839. key['default'] = value;
  840.  
  841. naifeitian.setValue(key['name'], key);
  842.  
  843. updateVideoPlayerCss();
  844.  
  845. isHolding = false;
  846. }
  847.  
  848. const onMouseDown = e => {
  849. isHolding = true;
  850.  
  851. const args = arguments;
  852. interval = setInterval(() => {
  853. const event = new Event('click');
  854. event.arguments = args;
  855.  
  856. e.target.dispatchEvent(event);
  857. }, 200);
  858. };
  859.  
  860. const onMouseUp = e => {
  861. clearInterval(interval);
  862. isHolding = false;
  863. };
  864.  
  865. $decBtn.addEventListener('click', onClick);
  866. $decBtn.addEventListener('mousedown', onMouseDown);
  867. $decBtn.addEventListener('mouseup', onMouseUp);
  868. $decBtn.addEventListener('touchstart', onMouseDown);
  869. $decBtn.addEventListener('touchend', onMouseUp);
  870.  
  871. $incBtn.addEventListener('click', onClick);
  872. $incBtn.addEventListener('mousedown', onMouseDown);
  873. $incBtn.addEventListener('mouseup', onMouseUp);
  874. $incBtn.addEventListener('touchstart', onMouseDown);
  875. $incBtn.addEventListener('touchend', onMouseUp);
  876.  
  877. return $wrapper;
  878. }
  879.  
  880. function setupVideoSettingsBar() {
  881. const CE = createElement;
  882. let $stretchInp;
  883. const refreshFunc = e => {
  884. updateVideoPlayerCss();
  885. };
  886. const $stretch = naifeitian.toElement(NFTconfig['video_stretch'], refreshFunc);
  887. const $wrapper = CE('div', { 'class': 'better-xcloud-quick-settings-bar' },
  888. CE('div', {},
  889. CE('label', { 'for': 'better-xcloud-quick-setting-stretch' }, '去黑边'),
  890. $stretch),
  891. CE('div', {},
  892. CE('label', {}, '清晰'),
  893. numberPicker(NFTconfig['VIDEO_CLARITY'], '', naifeitian.isSafari())),
  894. CE('div', {},
  895. CE('label', {}, '饱和'),
  896. numberPicker(NFTconfig['VIDEO_SATURATION'], '%')),
  897. CE('div', {},
  898. CE('label', {}, '对比'),
  899. numberPicker(NFTconfig['VIDEO_CONTRAST'], '%')),
  900. CE('div', {},
  901. CE('label', {}, '亮度'),
  902. numberPicker(NFTconfig['VIDEO_BRIGHTNESS'], '%'))
  903. );
  904.  
  905.  
  906. $stretch.addEventListener('change', e => {
  907. if (e.target.value == 'setting') {
  908. $('#video_stretch_x_y').css('display', 'block');
  909. } else {
  910. $('#video_stretch_x_y').css('display', 'none');
  911. }
  912. NFTconfig['video_stretch'].default = e.target.value;
  913. naifeitian.setValue('video_stretchGM', NFTconfig['video_stretch']);
  914. updateVideoPlayerCss();
  915. });
  916.  
  917. document.documentElement.appendChild($wrapper);
  918. if ($stretch.id == 'xcloud_setting_video_stretchGM') {
  919. let dom = $('#xcloud_setting_video_stretchGM');
  920. dom.after(`<div id="video_stretch_x_y" style="display: ${NFTconfig['video_stretch'].default == 'setting' ? 'block' : 'none'}">
  921. <lable>左右
  922. <input type=\'text\'class="video_stretch_x_y_Listener" id="video_stretch_x" style="width:35px" value="${NFTconfig['video_stretch_x_y']['x']}"/>
  923. </lable><br/>
  924. <lable>上下
  925. <input type=\'text\'class="video_stretch_x_y_Listener" id="video_stretch_y" style="width:35px" value="${NFTconfig['video_stretch_x_y']['y']}"/>
  926. </lable>
  927. </div>`);
  928.  
  929. $(document).on('blur', '.video_stretch_x_y_Listener', function () {
  930. let newval = $(this).val();
  931. if (naifeitian.isNumber($(this).val())) {
  932. if ($(this).attr('id') == 'video_stretch_x') {
  933. NFTconfig['video_stretch_x_y']['x'] = newval;
  934. naifeitian.setValue('video_stretch_x_yGM', NFTconfig['video_stretch_x_y']);
  935. } else {
  936. NFTconfig['video_stretch_x_y']['y'] = newval;
  937. naifeitian.setValue('video_stretch_x_yGM', NFTconfig['video_stretch_x_y']);
  938. }
  939. } else {
  940. $(this).val("0");
  941. NFTconfig['video_stretch_x_y']['x'] = 0;
  942. NFTconfig['video_stretch_x_y']['y'] = 0;
  943. naifeitian.setValue('video_stretch_x_yGM', NFTconfig['video_stretch_x_y']);
  944. }
  945. updateVideoPlayerCss();
  946. });
  947. }
  948. }
  949.  
  950. function cloneStreamMenuButton($orgButton, label, svg_icon) {
  951. const $button = $orgButton.cloneNode(true);
  952. $button.setAttribute('aria-label', label);
  953. $button.querySelector('div[class*=label]').textContent = label;
  954.  
  955. const $svg = $button.querySelector('svg');
  956. $svg.innerHTML = svg_icon;
  957. $svg.setAttribute('viewBox', '0 0 32 32');
  958.  
  959. return $button;
  960. }
  961.  
  962. function HookProperty(object, property, value) {
  963. Object.defineProperty(object, property, {
  964. value: value
  965. });
  966. }
  967. let windowCtx = self.window;
  968. if (self.unsafeWindow) { windowCtx = self.unsafeWindow; }
  969. let userAgentOriginal = windowCtx.navigator.userAgent;
  970. try {
  971. HookProperty(windowCtx.navigator, "userAgent",NFTconfig['CustomUAUser']);
  972. HookProperty(windowCtx.navigator, "maxTouchPoints", 10);
  973. if (NFTconfig['disableCheckNetwork'] == 1) {
  974. Object.defineProperty(windowCtx.navigator, 'connection', {
  975. get: () => undefined,
  976. });
  977. }
  978. HookProperty(windowCtx.navigator, "standalone", true);
  979.  
  980. } catch (e) { }
  981. windowCtx.fetch = (...arg) => {
  982. let arg0 = arg[0];
  983. let url = "";
  984. let isRequest = false;
  985. switch (typeof arg0) {
  986. case "object":
  987. url = arg0.url;
  988. isRequest = true;
  989. break;
  990. case "string":
  991. url = arg0;
  992. break;
  993. default:
  994. break;
  995. }
  996.  
  997. if (url.indexOf('/v2/login/user') > -1) {//xgpuweb.gssv-play-prod.xboxlive.com
  998. return new Promise((resolve, reject) => {
  999. if (isRequest && arg0.method == "POST") {
  1000. arg0.json().then(json => {
  1001. let body = JSON.stringify(json);
  1002. if (NFTconfig['no_need_VPN_play'] == 1) {
  1003. console.log('免代理开始');
  1004. if (NFTconfig['customfakeIp'] == 1 && naifeitian.isValidIP(NFTconfig['customfakeIp'])) {
  1005. arg[0].headers.set('x-forwarded-for', NFTconfig['customfakeIp']);
  1006. console.log('自定义IP:' + NFTconfig['customfakeIp']);
  1007. } else {
  1008. arg[0].headers.set('x-forwarded-for', NFTconfig['fakeIp']);
  1009. }
  1010. }
  1011.  
  1012. arg[0] = new Request(url, {
  1013. method: arg0.method,
  1014. headers: arg0.headers,
  1015. body: body,
  1016.  
  1017. });
  1018. originFetch(...arg).then(res => {
  1019. console.log('免代理结束');
  1020. res.json().then(json => {
  1021. let newServerList = [];
  1022. let currentAutoServer;
  1023. json["offeringSettings"]["regions"].forEach((region) => {
  1024. newServerList.push(region["name"]);
  1025. if (region["isDefault"] === true) {
  1026. currentAutoServer = region["name"];
  1027. }
  1028. });
  1029. naifeitian.setValue("blockXcloudServerListGM", newServerList);
  1030. NFTconfig['blockXcloudServerList'] = newServerList;
  1031.  
  1032. if (NFTconfig['blockXcloudServerList'].indexOf(NFTconfig['defaultXcloudServer']) == -1) {
  1033. naifeitian.setValue("defaultXcloudServerGM", "");
  1034. NFTconfig['defaultXcloudServer'] = "";
  1035. NFTconfig['blockXcloudServer'] = 0;
  1036. naifeitian.setValue("blockXcloudServerGM", 0);
  1037. }
  1038. if (NFTconfig['blockXcloudServer'] == 1) {
  1039. console.log('修改服务器开始');
  1040. json["offeringSettings"]["allowRegionSelection"] = true;
  1041. let selectedServer = NFTconfig['defaultXcloudServer'];
  1042. if (selectedServer !== "Auto" && newServerList.includes(selectedServer)) {
  1043. json["offeringSettings"]["regions"].forEach((region) => {
  1044. if (region["name"] === selectedServer) {
  1045. region["isDefault"] = true;
  1046. } else {
  1047. region["isDefault"] = false;
  1048. }
  1049. });
  1050. }
  1051. console.log('修改服务器结束');
  1052. }
  1053.  
  1054.  
  1055. try {
  1056. json["offeringSettings"]["regions"].forEach((region) => {
  1057. if(region.isDefault){
  1058. StreamBadges.region = region.name;
  1059. throw new Error();
  1060. }
  1061. });
  1062.  
  1063. } catch(e) {}
  1064.  
  1065. let body = JSON.stringify(json);
  1066. let newRes = new Response(body, {
  1067. status: res.status,
  1068. statusText: res.statusText,
  1069. headers: res.headers
  1070. })
  1071. resolve(newRes);
  1072. }).catch(err => {
  1073. reject(err);
  1074. });
  1075. }).catch(err => {
  1076. reject(err);
  1077. });
  1078. });
  1079.  
  1080. } else {
  1081. console.error("[ERROR] Not a request.");
  1082. return originFetch(...arg);
  1083. }
  1084. });
  1085. } else if (url.indexOf('/cloud/play') > -1) {
  1086.  
  1087. document.documentElement.style.overflowY = "hidden";
  1088.  
  1089. document.body.style.top = '0px';
  1090. if (NFTconfig['autoFullScreen'] == 1) {
  1091. setMachineFullScreen();
  1092. }
  1093. if (NFTconfig['noPopSetting'] == 0) {
  1094. $('#popSetting').css('display', 'none');
  1095. }
  1096.  
  1097. if (NFTconfig['chooseLanguage'] == 1) {
  1098. return new Promise(async (resolve, reject) => {
  1099. console.log('语言开始');
  1100. let selectedLanguage = NFTconfig['xcloud_game_language'];
  1101. console.log('语言选择:' + selectedLanguage);
  1102. if (selectedLanguage == 'Auto') {
  1103.  
  1104. let parts=window.location.pathname.split('/');
  1105. let pid = parts[parts.length-1];
  1106. try {
  1107. let res = await fetch(
  1108. "https://catalog.gamepass.com/products?market=US&language=en-US&hydration=PCInline", {
  1109. "headers": {
  1110. "content-type": "application/json;charset=UTF-8",
  1111. },
  1112. "body": "{\"Products\":[\"" + pid + "\"]}",
  1113. "method": "POST",
  1114. "mode": "cors",
  1115. "credentials": "omit"
  1116. });
  1117. let jsonObj = await res.json();
  1118. let languageSupport = jsonObj["Products"][pid]["LanguageSupport"]
  1119. for (let language of Object.keys(default_language_list)) {
  1120. if (default_language_list[language] in languageSupport) {
  1121. selectedLanguage = default_language_list[language];
  1122. break;
  1123. }
  1124. }
  1125. if (selectedLanguage == 'Auto') {
  1126. //防止接口没有返回支持语言
  1127. selectedLanguage = NFTconfig['IfErrUsedefaultGameLanguage'];
  1128. }
  1129.  
  1130. } catch (e) {
  1131. }
  1132. }
  1133.  
  1134. if (isRequest && arg0.method == "POST") {
  1135. arg0.json().then(json => {
  1136.  
  1137. json["settings"]["locale"] = selectedLanguage;
  1138.  
  1139. json["settings"]["osName"] = NFTconfig['high_bitrate'] == 1 ? 'windows' : 'android';
  1140. let body = JSON.stringify(json);
  1141.  
  1142. arg[0] = new Request(url, {
  1143. method: arg0.method,
  1144. headers: arg0.headers,
  1145. body: body,
  1146. mode: arg0.mode,
  1147. credentials: arg0.credentials,
  1148. cache: arg0.cache,
  1149. redirect: arg0.redirect,
  1150. referrer: arg0.referrer,
  1151. integrity: arg0.integrity
  1152. });
  1153. originFetch(...arg).then(res => {
  1154. console.log(`语言结束, 选择语言: ${selectedLanguage}.`)
  1155. resolve(res);
  1156.  
  1157. }).catch(err => {
  1158. reject(err);
  1159. });
  1160. });
  1161. } else {
  1162. console.error("[ERROR] Not a request.");
  1163. return originFetch(...arg);
  1164. }
  1165. });
  1166. } else {
  1167. return originFetch(...arg);
  1168. }
  1169. } else if (url.indexOf('/configuration') > -1 && NFTconfig['autoOpenOC'] == 1 && NFTconfig['disableTouchControls'] == 0) {
  1170. // Enable CustomTouchOverlay
  1171. console.log('修改触摸开始')
  1172. return new Promise((resolve, reject) => {
  1173. originFetch(...arg).then(res => {
  1174. res.json().then(json => {
  1175. // console.error(json);
  1176. let inputOverrides = JSON.parse(json.clientStreamingConfigOverrides || '{}') || {};
  1177. inputOverrides.inputConfiguration = {
  1178. enableTouchInput: true,
  1179. maxTouchPoints: 10,
  1180. enableVibration:true
  1181. };
  1182. json.clientStreamingConfigOverrides = JSON.stringify(inputOverrides);
  1183. let cdom = $('#BabylonCanvasContainer-main').children();
  1184. if (cdom.length > 0) {
  1185. NFTconfig['canShowOC'] = false;
  1186. } else {
  1187. NFTconfig['canShowOC'] = true;
  1188. }
  1189. let body = JSON.stringify(json);
  1190. let newRes = new Response(body, {
  1191. status: res.status,
  1192. statusText: res.statusText,
  1193. headers: res.headers
  1194. })
  1195. resolve(newRes);
  1196.  
  1197. console.log('修改触摸结束')
  1198. }).catch(err => {
  1199. reject(err);
  1200. });
  1201. }).catch(err => {
  1202. reject(err);
  1203. });
  1204. });
  1205. } 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
  1206. return originFetch(...arg).then(response => {
  1207. return response.clone().text().then(text => {
  1208. if (!text.length) {
  1209. return response;
  1210. }
  1211.  
  1212. const obj = JSON.parse(text);
  1213. let exchangeResponse = JSON.parse(obj.exchangeResponse);
  1214. exchangeResponse = updateIceCandidates(exchangeResponse)
  1215. obj.exchangeResponse = JSON.stringify(exchangeResponse);
  1216.  
  1217. response.json = () => Promise.resolve(obj);
  1218. response.text = () => Promise.resolve(JSON.stringify(obj));
  1219.  
  1220. return response;
  1221. });
  1222. });
  1223. }else {
  1224. return originFetch(...arg);
  1225. }
  1226. }
  1227. function updateIceCandidates(candidates) {
  1228. const pattern = new RegExp(/a=candidate:(?<foundation>\d+) (?<component>\d+) UDP (?<priority>\d+) (?<ip>[^\s]+) (?<the_rest>.*)/);
  1229.  
  1230. const lst = [];
  1231. for (let item of candidates) {
  1232. if (item.candidate == 'a=end-of-candidates') {
  1233. continue;
  1234. }
  1235.  
  1236. const groups = pattern.exec(item.candidate).groups;
  1237. lst.push(groups);
  1238. }
  1239.  
  1240. lst.sort((a, b) => (a.ip.includes(':') || a.ip > b.ip) ? -1 : 1);
  1241.  
  1242. const newCandidates = [];
  1243. let foundation = 1;
  1244. lst.forEach(item => {
  1245. item.foundation = foundation;
  1246. item.priority = (foundation == 1) ? 100 : 1;
  1247.  
  1248. newCandidates.push({
  1249. 'candidate': `a=candidate:${item.foundation} 1 UDP ${item.priority} ${item.ip} ${item.the_rest}`,
  1250. 'messageType': 'iceCandidate',
  1251. 'sdpMLineIndex': '0',
  1252. 'sdpMid': '0',
  1253. });
  1254.  
  1255. ++foundation;
  1256. });
  1257.  
  1258. newCandidates.push({
  1259. 'candidate': 'a=end-of-candidates',
  1260. 'messageType': 'iceCandidate',
  1261. 'sdpMLineIndex': '0',
  1262. 'sdpMid': '0',
  1263. });
  1264.  
  1265. return newCandidates;
  1266. }
  1267. function checkCodec(){
  1268. let video_quality= naifeitian.getValue('video_qualityGM');
  1269. let codecs=RTCRtpReceiver.getCapabilities('video').codecs;
  1270. let codesOptions=['默认'];
  1271. const codecProfileMap = {"高": "4d","中": "42e","低": "420"};
  1272. codecs.forEach((codec, index) => {
  1273. if (codec.mimeType === 'video/H264') {
  1274. for (let key in codecProfileMap) {
  1275. if (codec.sdpFmtpLine.includes(codecProfileMap[key])) {
  1276. codesOptions.push(codec.mimeType.substring(6)+key);
  1277. break;
  1278. }
  1279. }
  1280. }else{
  1281. codesOptions.push(codec.mimeType.substring(6));
  1282. }
  1283. });
  1284.  
  1285. codesOptions = [...new Set(codesOptions)];
  1286.  
  1287. let sortOrder = ['默认','AV1', 'VP9', 'H265', 'VP8','H264高','H264中','H264低','flexfec-03','ulpfec','rtx','red'];
  1288. const customSort = (a, b) => {
  1289. const indexOfA = sortOrder.indexOf(a);
  1290. const indexOfB = sortOrder.indexOf(b);
  1291.  
  1292. if (indexOfA === -1) {
  1293. return 1;
  1294. }
  1295. if (indexOfB === -1) {
  1296. return -1;
  1297. }
  1298. return indexOfA - indexOfB;
  1299. };
  1300. codesOptions.sort(customSort);
  1301. video_quality['options']=codesOptions;
  1302.  
  1303. if (!video_quality['options'].includes(video_quality['default'])) {
  1304. video_quality['default']="默认";
  1305. }
  1306. NFTconfig['video_quality']=video_quality;
  1307. naifeitian.setValue('video_qualityGM',video_quality);
  1308. }
  1309. checkCodec();
  1310.  
  1311. if (NFTconfig['autoOpenOC'] == 1 && NFTconfig['disableTouchControls'] == 0 && NFTconfig['autoShowTouch']) {
  1312. windowCtx.RTCPeerConnection.prototype.originalCreateDataChannelGTC = windowCtx.RTCPeerConnection.prototype.createDataChannel;
  1313. windowCtx.RTCPeerConnection.prototype.createDataChannel = function (...params) {
  1314. let dc = this.originalCreateDataChannelGTC(...params);
  1315. let paddingMsgTimeoutId = 0;
  1316. if (dc.label == "message") {
  1317. dc.addEventListener("message", function (de) {
  1318. if (typeof (de.data) == "string") {
  1319. let msgdata = JSON.parse(de.data);
  1320. if (msgdata.target == "/streaming/touchcontrols/showlayoutv2") {
  1321. clearTimeout(paddingMsgTimeoutId);
  1322. } else if (msgdata.target == "/streaming/touchcontrols/showtitledefault") {
  1323.  
  1324. if (!NFTconfig['canShowOC']) {
  1325. clearTimeout(paddingMsgTimeoutId);
  1326. } else {
  1327. if (msgdata.pluginHookMessage !== true) {
  1328. clearTimeout(paddingMsgTimeoutId);
  1329. paddingMsgTimeoutId = setTimeout(() => {
  1330. dc.dispatchEvent(new MessageEvent('message', {
  1331. data: '{"content":"{\\"layoutId\\":\\"\\"}","target":"/streaming/touchcontrols/showlayoutv2","type":"Message","pluginHookMessage":true}'
  1332. }));
  1333. }, 1000);
  1334. }
  1335. }
  1336. }
  1337. }
  1338. });
  1339. }
  1340. return dc;
  1341. }
  1342. }
  1343.  
  1344. // 配置对象,定义每个设置项的信息
  1345. const settingsConfig = [
  1346. {
  1347. label: '选择语言:',
  1348. type: 'radio',
  1349. name: 'chooseLanguage',
  1350. display: 'block',
  1351. options: [
  1352. { value: 1, text: '开', id: 'chooseLanguageOn' },
  1353. { value: 0, text: '关', id: 'chooseLanguageOff' }
  1354. ],
  1355. checkedValue: NFTconfig['chooseLanguage'],
  1356. needHr: false
  1357. },
  1358. {
  1359. label: '语言:',
  1360. type: 'radio',
  1361. name: 'selectLanguage',
  1362. display: NFTconfig['chooseLanguage'] === 1 ? 'block' : 'none',
  1363. options: Object.keys(default_language_list).map(languageChinese => {
  1364. return {
  1365. value: default_language_list[languageChinese],
  1366. text: languageChinese,
  1367. id: default_language_list[languageChinese]
  1368. };
  1369. }),
  1370. checkedValue: NFTconfig['xcloud_game_language'],
  1371. needHr: false
  1372.  
  1373. },
  1374. {
  1375. label: '智能错误时使用:',
  1376. type: 'radio',
  1377. name: 'IfErrUsedefaultGameLanguage',
  1378. display: NFTconfig['xcloud_game_language'] === 'Auto' ? 'block' : 'none',
  1379. options: Object.keys(default_language_list).map(languageChinese => {
  1380. if (languageChinese == '智能简繁') { return; }
  1381. return {
  1382. value: default_language_list[languageChinese],
  1383. text: languageChinese,
  1384. id: default_language_list[languageChinese] + 'ifErr'
  1385. };
  1386.  
  1387. }),
  1388. checkedValue: NFTconfig['IfErrUsedefaultGameLanguage'],
  1389. needHr: true
  1390. },
  1391. {
  1392. label: '免代理直连:',
  1393. type: 'radio',
  1394. name: 'noNeedVpn',
  1395. display: 'block',
  1396. options: [
  1397. { value: 1, text: '开', id: 'noNeedVpnOn' },
  1398. { value: 0, text: '关', id: 'noNeedVpnOff' },
  1399. ],
  1400. checkedValue: NFTconfig['no_need_VPN_play'],
  1401. needHr: false
  1402. },
  1403. {
  1404. label: '选服:',
  1405. type: 'radio',
  1406. name: 'selectRegion',
  1407. display: NFTconfig['no_need_VPN_play'] === 1 ? 'block' : 'none',
  1408. options: Object.keys(NFTconfig['regionsList']).map(region => {
  1409. return {
  1410. value: NFTconfig['regionsList'][region],
  1411. text: region,
  1412. id: NFTconfig['regionsList'][region]
  1413. };
  1414. }),
  1415. checkedValue: NFTconfig['fakeIp'],
  1416. needHr: false
  1417. },
  1418. {
  1419. label: '自定义IP:',
  1420. type: 'radio',
  1421. name: 'customfakeIpInput',
  1422. display: NFTconfig['no_need_VPN_play'] === 1 ? 'block' : 'none',
  1423. value: NFTconfig['customfakeIp'],
  1424. needHr: true,
  1425. moreDom: `<input type="radio" class="selectRegionListener settingsBoxInputRadio" style="outline:none;"
  1426. name='selectRegion' id="customfakeIp" value="customfakeIp" ${NFTconfig['customfakeIp'] == 1 ? 'checked' : ''}>
  1427. <label for="customfakeIp" style="padding-right: 15px;">自定义IP:</label>
  1428. <input type='text' style="display: ` + (NFTconfig['customfakeIp'] == 1 ? 'inline' : 'none')
  1429. + `;outline: none;width: 125px;" id="customfakeIpInput" class="customfakeIpListener" value="${NFTconfig['customfakeIp']}" placeholder="请输入IP"/>`
  1430.  
  1431. },
  1432. {
  1433. label: '分辨率:',
  1434. type: 'radio',
  1435. name: 'highBitrate',
  1436. display: 'block',
  1437. options: [
  1438. { value: 1, text: '1080P', id: 'high_bitrateOn' },
  1439. { value: 0, text: '720P', id: 'high_bitrateOff' }
  1440. ],
  1441. checkedValue: NFTconfig['high_bitrate'],
  1442. needHr: true
  1443. },
  1444. {
  1445. label: '视频编解码偏好:',
  1446. showLable:true,
  1447. type: 'dropdown',
  1448. name: 'video_quality',
  1449. display: "block",
  1450. options: NFTconfig['video_quality']['options'],
  1451. selectedValue: NFTconfig['video_quality']['default'],
  1452. needHr: true
  1453. },
  1454. {
  1455. label: '禁止检测网络状况:',
  1456. type: 'radio',
  1457. name: 'disableCheckNetwork',
  1458. display: 'block',
  1459. options: [
  1460. { value: 1, text: '开', id: 'disableCheckNetworkOn' },
  1461. { value: 0, text: '关', id: 'disableCheckNetworkOff' }
  1462. ],
  1463. checkedValue: NFTconfig['disableCheckNetwork'],
  1464. needHr: true
  1465. },
  1466. {
  1467. label: '强制触控:',
  1468. type: 'radio',
  1469. name: 'autoOpenOC',
  1470. display: 'block',
  1471. options: [
  1472. { value: 1, text: '开', id: 'autoOpenOCOn' },
  1473. { value: 0, text: '关', id: 'autoOpenOCOff' }
  1474. ],
  1475. checkedValue: NFTconfig['autoOpenOC'],
  1476. needHr: true,
  1477. moreDom: `<div id="autoShowTouchDom" style="padding-right: 0px;display: ${NFTconfig['autoOpenOC'] == 1 ? 'inline' : 'none'}">
  1478. <input type="checkbox" class="autoShowTouchListener settingsBoxInputRadio" style="outline:none;cursor: pointer;" name='autoShowTouch'
  1479. id="autoShowTouch" ${NFTconfig['autoShowTouch'] == true ? 'checked' : ''}><label for="autoShowTouch" style="cursor: pointer;">自动弹出</label></div>`
  1480. },
  1481. {
  1482.  
  1483. label: '手势显隐触控:',
  1484. type: 'radio',
  1485. name: 'slideToHide',
  1486. display: 'block',
  1487. options: [
  1488. { value: 1, text: '开', id: 'slideToHideOn' },
  1489. { value: 0, text: '关', id: 'slideToHideOff' },
  1490. ],
  1491. checkedValue: NFTconfig['slideToHide'],
  1492. needHr: true
  1493. },
  1494. {
  1495. label: '屏蔽触控:',
  1496. type: 'radio',
  1497. name: 'disableTouchControls',
  1498. display: 'block',
  1499. options: [
  1500. { value: 1, text: '开', id: 'disableTouchControlsOn' },
  1501. { value: 0, text: '关', id: 'disableTouchControlsOff' },
  1502. ],
  1503. checkedValue: NFTconfig['disableTouchControls'],
  1504. needHr: true
  1505. },
  1506. {
  1507. label: '自动全屏:',
  1508. type: 'radio',
  1509. name: 'autoFullScreen',
  1510. display: 'block',
  1511. options: [
  1512. { value: 1, text: '开', id: 'autoFullScreenOn' },
  1513. { value: 0, text: '关', id: 'autoFullScreenOff' }
  1514. ],
  1515. checkedValue: NFTconfig['autoFullScreen'],
  1516. needHr: true
  1517. },
  1518. {
  1519. label: 'IPv6:',
  1520. type: 'radio',
  1521. name: 'IPv6server',
  1522. display: 'block',
  1523. options: [
  1524. { value: 1, text: '开', id: 'IPv6On' },
  1525. { value: 0, text: '关', id: 'IPv6Off' }
  1526. ],
  1527. checkedValue: NFTconfig['IPv6'],
  1528. needHr: true
  1529. }
  1530. ,
  1531. {
  1532. label: '物理服务器:',
  1533. type: 'radio',
  1534. name: 'blockXcloudServer',
  1535. display: 'block',
  1536. options: [
  1537. { value: 1, text: '开', id: 'blockXcloudServerOn' },
  1538. { value: 0, text: '关', id: 'blockXcloudServerOff' }
  1539. ],
  1540. checkedValue: NFTconfig['blockXcloudServer'],
  1541. needHr: false
  1542. },
  1543. {
  1544. label: '选择服务器:',
  1545. type: 'dropdown',
  1546. name: 'defaultXcloudServer',
  1547. display: NFTconfig['blockXcloudServer'] === 1?"block":"none",
  1548. options: NFTconfig['blockXcloudServerList'],
  1549. selectedValue: NFTconfig['defaultXcloudServer'],
  1550. needHr: true
  1551.  
  1552. },
  1553. {
  1554. label: '挂机防踢:',
  1555. type: 'radio',
  1556. name: 'antiKick',
  1557. display: 'block',
  1558. options: [
  1559. { value: 1, text: '开', id: 'antiKickOn' },
  1560. { value: 0, text: '关', id: 'antiKickOff' }
  1561. ],
  1562. checkedValue: NFTconfig['antiKick'],
  1563. needHr: true
  1564.  
  1565. },
  1566. {
  1567. label: '设置悬浮窗:',
  1568. type: 'radio',
  1569. name: 'noPopSetting',
  1570. display: 'block',
  1571. options: [
  1572. { value: 0, text: '显示', id: 'noPopSettingOff' },
  1573. { value: 1, text: '隐藏', id: 'noPopSettingOn' }
  1574. ],
  1575. checkedValue: NFTconfig['noPopSetting'],
  1576. needHr: true
  1577. },
  1578. {
  1579. label: 'User-Agent:',
  1580. showLable:true,
  1581. type: 'dropdown',
  1582. css:'width:90%',
  1583. name: 'User-Agent',
  1584. display: "block",
  1585. options: NFTconfig['CustomUA']['options'],
  1586. optionsCss:'float:right',
  1587. selectedValue: NFTconfig['CustomUA']['default'],
  1588. needHr: true,
  1589. moreDom: `<br><input type="text" style="display: inline; outline: none; width: 100%;margin-top:12px;" `
  1590. + (NFTconfig['CustomUA']['default']!='自定义'?"readonly disable":"")
  1591. + ` id="customUAuser" class="customUAuserListener" value="${NFTconfig['CustomUAUser']}">`
  1592. },
  1593.  
  1594.  
  1595. ];
  1596.  
  1597. // 函数用于生成单个设置项的HTML
  1598. function generateSettingElement(setting) {
  1599. let settingHTML = `<lable style="display:${setting.display};white-space: nowrap;" class="${setting.name + 'Dom'}">`;
  1600. if (setting.type === 'radio') {
  1601. if (setting.options != undefined) {
  1602. settingHTML += `<label style="display:block;text-align:left;"><div style="display: inline;">${setting.label}</div>`;
  1603. setting.options.forEach(option => {
  1604. if (option == null) { return; }
  1605.  
  1606. settingHTML += `
  1607. <input type="radio" class="${setting.name + 'Listener'} settingsBoxInputRadio" style="outline:none;" name="${setting.name}" id="${option.id}" value="${option.value}" ${option.value === setting.checkedValue ? 'checked' : ''}>
  1608. <label for="${option.id}" style="padding-right: 15px;cursor: pointer;">${option.text}</label>
  1609. `;
  1610. });
  1611. }
  1612. if (setting.moreDom != undefined) {
  1613. settingHTML += setting.moreDom;
  1614. }
  1615. settingHTML += '</label>';
  1616. } else if (setting.type === 'text') {
  1617. settingHTML += `<label style="display: display:block;text-align:left;"><div style="display: inline;">${setting.label}</div>`;
  1618. settingHTML += `
  1619. <input type="text" style="display: inline;outline: none;width: 125px;" id="${setting.name}" class="${setting.name}Listener" value="${setting.value}" placeholder="请输入${setting.label}"/>
  1620. `;
  1621. settingHTML += `</label>`;
  1622. } else if (setting.type === 'dropdown') {
  1623. if(setting.showLable==true){
  1624. settingHTML += `<label style="display: display:block;text-align:left;${setting.css}"><div style="display: inline;">${setting.label}</div>`;
  1625. }
  1626. if(setting.options.length==undefined){
  1627. setting.options=Object.keys(setting.options);
  1628. }
  1629. settingHTML += `
  1630. <select style="outline: none;margin-bottom:5px;${setting.optionsCss}" class="${setting.name + 'Listener'}">
  1631. ${setting.options.map(option => `<option value="${option}" ${option === setting.selectedValue ? 'selected' : ''}>${option}</option>`).join('')}
  1632. </select>
  1633. `;
  1634.  
  1635. if (setting.moreDom != undefined) {
  1636. settingHTML += setting.moreDom;
  1637. }
  1638. }
  1639.  
  1640. settingHTML += `</lable>`;
  1641.  
  1642. if (setting.needHr) {
  1643. settingHTML += `<hr style="background-color: black;width:95%" />`
  1644. }
  1645. return settingHTML;
  1646. }
  1647. function generateSettingsPage() {
  1648. let settingsHTML = `
  1649. <div style="padding: 10px;color: black;display:none;" class="settingsBackgroud" id="settingsBackgroud">
  1650. <div class="settingsBox"><span class="blink-text" onclick="window.location.href='https://gf.qytechs.cn/zh-CN/scripts/455741';">更新咯~</span>
  1651. `;
  1652. settingsConfig.forEach(setting => {
  1653. settingsHTML += generateSettingElement(setting);
  1654. });
  1655.  
  1656. settingsHTML += `
  1657. <button class="closeSetting1 closeSetting2" style="outline: none;">关闭</button>
  1658. <div style="text-align: right;margin-top: 8px;font-size: 16px;">
  1659. <label>捐赠:</label>
  1660. <a style="margin-right:15px;outline: none;color: #107c10;text-decoration: underline;" href="https://gf.qytechs.cn/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBMVNFQVE9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--14c278e3f72d104cff50bf130d4039229fc25a6b/wx.png?locale=zh-CN">微信</a>
  1661. <a style="outline: none;color: #107c10;text-decoration: underline;" href="https://gf.qytechs.cn/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBMU9FQVE9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--5fc08aaa8407cc6099654d65455b7966bf2c60ee/alipay.png?locale=zh-CN">支付宝</a>
  1662. </div>
  1663. </div>
  1664. </div>
  1665. `;
  1666.  
  1667. return settingsHTML;
  1668. }
  1669. let needrefresh = 0;
  1670. function initSettingBox() {
  1671. $('body').append(generateSettingsPage());
  1672.  
  1673. //确定
  1674. $(document).on('click', '.closeSetting1', function () {
  1675.  
  1676. naifeitian.hideSetting();
  1677. if (needrefresh == 1) {
  1678. history.go(0);
  1679. }
  1680. });
  1681. //ua输入框
  1682. $(document).on('blur', '.customUAuserListener', function () {
  1683. if($(this).attr('readonly')=='readonly'){return;}
  1684. if($(this).val()==""){
  1685. alert("请输入ua");
  1686. return;
  1687. }
  1688. NFTconfig['CustomUA']['options']['自定义']=$(this).val();
  1689. naifeitian.setValue('CustomUAGM',NFTconfig['CustomUA']);
  1690. naifeitian.setValue('CustomUAUserGM',$(this).val());
  1691. needrefresh = 1;
  1692. $('.closeSetting1').text('确定');
  1693. });
  1694. //ua
  1695. $(document).on('change', '.User-AgentListener', function () {
  1696. if($(this).val()=='自定义'){
  1697. $('#customUAuser').removeAttr('readonly');
  1698. $('#customUAuser').removeAttr('disable');
  1699. }else{
  1700. $("#customUAuser").prop('readonly', true);
  1701. $("#customUAuser").prop('disable', true);
  1702. }
  1703. naifeitian.setValue('CustomUAUserGM',NFTconfig['CustomUA']['options'][$(this).val()]);
  1704. NFTconfig['CustomUA']['default']=$(this).val();
  1705. naifeitian.setValue('CustomUAGM',NFTconfig['CustomUA']);
  1706. $('#customUAuser').val(NFTconfig['CustomUA']['options'][NFTconfig['CustomUA']['default']])
  1707. needrefresh = 1;
  1708. $('.closeSetting1').text('确定');
  1709. });
  1710. //设置悬浮窗
  1711. $(document).on('click', '.noPopSettingListener', function () {
  1712. naifeitian.setValue('noPopSettingGM', $(this).val());
  1713. needrefresh = 1;
  1714. $('.closeSetting1').text('确定');
  1715. });
  1716. //挂机防踢
  1717. $(document).on('click', '.antiKickListener', function () {
  1718. needrefresh = 1;
  1719. naifeitian.setValue('antiKickGM', $(this).val());
  1720. $('.closeSetting1').text('确定');
  1721. });
  1722. //ipv6
  1723. $(document).on('click', '.IPv6serverListener', function () {
  1724. naifeitian.setValue('IPv6GM', $(this).val());
  1725. needrefresh = 1;
  1726. $('.closeSetting1').text('确定');
  1727. });
  1728. //选择服务器change
  1729. $(document).on('change', '.defaultXcloudServerListener', function () {
  1730. naifeitian.setValue('defaultXcloudServerGM', $(this).val());
  1731. needrefresh = 1;
  1732. $('.closeSetting1').text('确定');
  1733. });
  1734. //物理服务器
  1735. $(document).on('click', '.blockXcloudServerListener', function () {
  1736. if ($(this).val() == 0) {
  1737. $('.defaultXcloudServerDom').css('display', 'none');
  1738. } else {
  1739. $('.defaultXcloudServerDom').css('display', 'block');
  1740. }
  1741. naifeitian.setValue('blockXcloudServerGM', $(this).val());
  1742. needrefresh = 1;
  1743. $('.closeSetting1').text('确定');
  1744. });
  1745.  
  1746. //自动全屏
  1747. $(document).on('click', '.autoFullScreenListener', function () {
  1748. naifeitian.setValue('autoFullScreenGM', $(this).val());
  1749. needrefresh = 1;
  1750. $('.closeSetting1').text('确定');
  1751. });
  1752. //屏蔽触控
  1753. $(document).on('click', '.disableTouchControlsListener', function () {
  1754. if ($(this).val() == 1) {
  1755. if (!confirm("确定要屏蔽触控吗?")) {
  1756. $('#disableTouchControlsOff').click();
  1757. return;
  1758. }
  1759. $('#autoOpenOCOff').click();
  1760. $('#slideToHideOff').click();
  1761. }
  1762.  
  1763. needrefresh = 1;
  1764. naifeitian.setValue('disableTouchControlsGM', $(this).val());
  1765. $('.closeSetting1').text('确定');
  1766. });
  1767.  
  1768. //自动弹出
  1769. $(document).on('change', '.autoShowTouchListener', function () {
  1770. let newVal = $(this).attr('checked') == 'checked';
  1771. if (newVal) {
  1772. $(this).removeAttr('checked');
  1773. } else {
  1774. $(this).attr('checked');
  1775. }
  1776. naifeitian.setValue('autoShowTouchGM', !newVal);
  1777. needrefresh = 1;
  1778. $('.closeSetting1').text('确定');
  1779. });
  1780. //手势显隐触控
  1781. $(document).on('click', '.slideToHideListener', function () {
  1782.  
  1783. if ($(this).val() == 1) {
  1784. $('#disableTouchControlsOff').click();
  1785. $('#autoOpenOCOn').click();
  1786.  
  1787. }
  1788. naifeitian.setValue('slideToHideGM', $(this).val());
  1789. needrefresh = 1;
  1790. $('.closeSetting1').text('确定');
  1791. });
  1792. //强制触控
  1793. $(document).on('click', '.autoOpenOCListener', function () {
  1794.  
  1795. if ($(this).val() == 0) {
  1796. $('#autoShowTouchDom').css('display', 'none');
  1797. } else {
  1798. $('#autoShowTouchDom').css('display', 'inline');
  1799. $('#disableTouchControlsOff').click();
  1800. }
  1801.  
  1802. naifeitian.setValue('autoOpenOCGM', $(this).val());
  1803. needrefresh = 1;
  1804. $('.closeSetting1').text('确定');
  1805. });
  1806.  
  1807. //禁止检测网络
  1808. $(document).on('click', '.disableCheckNetworkListener', function () {
  1809. naifeitian.setValue('disableCheckNetworkGM', $(this).val());
  1810. needrefresh = 1;
  1811. $('.closeSetting1').text('确定');
  1812. });
  1813.  
  1814. //画质
  1815. $(document).on('change', '.video_qualityListener', function () {
  1816. NFTconfig['video_quality']['default']=$(this).val();
  1817. naifeitian.setValue('video_qualityGM', NFTconfig['video_quality']);
  1818. needrefresh = 1;
  1819. $('.closeSetting1').text('确定');
  1820. });
  1821. //分辨率
  1822. $(document).on('click', '.highBitrateListener', function () {
  1823. naifeitian.setValue('high_bitrateGM', $(this).val());
  1824. needrefresh = 1;
  1825. $('.closeSetting1').text('确定');
  1826. });
  1827.  
  1828.  
  1829. //自定义ip输入框
  1830. $(document).on('blur', '.customfakeIpListener', function () {
  1831. if (naifeitian.isValidIP($(this).val())) {
  1832. naifeitian.setValue('customfakeIpGM', $(this).val());
  1833. } else {
  1834. $(this).val("");
  1835. naifeitian.setValue('customfakeIpGM', '');
  1836. alert('IP格式错误!');
  1837. return;
  1838. }
  1839. needrefresh = 1;
  1840. $('.closeSetting1').text('确定');
  1841. });
  1842. //选服
  1843. $(document).on('click', '.selectRegionListener', function () {
  1844. if ($(this).val() == 'customfakeIp') {
  1845. naifeitian.setValue('useCustomfakeIpGM', 1);
  1846. $('#customfakeIpInput').css('display', 'inline');
  1847. } else {
  1848. naifeitian.setValue('fakeIpGM', $(this).val());
  1849. naifeitian.setValue('useCustomfakeIpGM', 0);
  1850. $('#customfakeIpInput').css('display', 'none');
  1851. }
  1852. needrefresh = 1;
  1853. $('.closeSetting1').text('确定');
  1854. });
  1855.  
  1856. //免代理直连
  1857. $(document).on('click', '.noNeedVpnListener', function () {
  1858. if ($(this).val() == 0) {
  1859. $('.selectRegionDom').css('display', 'none');;
  1860. $('.customfakeIpInputDom').css('display', 'none');
  1861. } else {
  1862. $('.selectRegionDom').css('display', 'block');
  1863. $('.customfakeIpInputDom').css('display', 'block');
  1864. }
  1865. naifeitian.setValue('no_need_VPN_playGM', $(this).val());
  1866. needrefresh = 1;
  1867. $('.closeSetting1').text('确定');
  1868. });
  1869.  
  1870. //智能简繁错误
  1871. $(document).on('click', '.IfErrUsedefaultGameLanguageListener', function () {
  1872. naifeitian.setValue('IfErrUsedefaultGameLanguageGM', $(this).val());
  1873. needrefresh = 1;
  1874. $('.closeSetting1').text('确定');
  1875. });
  1876. //语言
  1877. $(document).on('click', '.selectLanguageListener', function () {
  1878. if ($(this).val() != 'Auto') {
  1879. $('.IfErrUsedefaultGameLanguageDom').css('display', 'none');
  1880. } else {
  1881. $('.IfErrUsedefaultGameLanguageDom').css('display', 'block');
  1882. }
  1883. naifeitian.setValue('xcloud_game_languageGM', $(this).val());
  1884. needrefresh = 1;
  1885. $('.closeSetting1').text('确定');
  1886. });
  1887.  
  1888. //选择语言
  1889. $(document).on('click', '.chooseLanguageListener', function () {
  1890. if ($(this).val() == 0) {
  1891. $('.selectLanguageDom').css('display', 'none');
  1892. $('.IfErrUsedefaultGameLanguageDom').css('display', 'none');
  1893. } else {
  1894. $('.selectLanguageDom').css('display', 'block');
  1895.  
  1896. if (naifeitian.getValue('xcloud_game_languageGM') == 'Auto') {
  1897. $('.IfErrUsedefaultGameLanguageDom').css('display', 'block');
  1898. }
  1899. }
  1900. naifeitian.setValue('chooseLanguageGM', $(this).val());
  1901. needrefresh = 1;
  1902. $('.closeSetting1').text('确定');
  1903. });
  1904. }
  1905.  
  1906. function initSlideHide(){
  1907. if(NFTconfig['slideToHide']==1){
  1908. var gestureArea = $("<div></div>");
  1909. gestureArea.attr("id", "touchControllerEventArea");
  1910. $(document.documentElement).append(gestureArea);
  1911.  
  1912. gestureArea = $("#touchControllerEventArea");
  1913. let startX, startY, endX, endY;
  1914. let threshold = 130; // 手势滑动的阈值
  1915. gestureArea.on("touchstart", function (e) {
  1916. startX = e.originalEvent.touches[0].clientX;
  1917. startY = e.originalEvent.touches[0].clientY;
  1918. });
  1919. gestureArea.on("touchmove", function (e) {
  1920. endX = e.originalEvent.touches[0].clientX;
  1921. endY = e.originalEvent.touches[0].clientY;
  1922. });
  1923. gestureArea.on("touchend", function (e) {
  1924. if (startX !== undefined && startY !== undefined && endX !== undefined && endY !== undefined) {
  1925. const deltaX = endX - startX;
  1926. const deltaY = endY - startY;
  1927. if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > threshold) {
  1928. if (deltaX < 0) {
  1929. // 左滑
  1930. $('#BabylonCanvasContainer-main').css('display','none');
  1931. $('#MultiTouchSurface').css('display','none');
  1932. e.preventDefault();
  1933. } else {
  1934. // 右滑
  1935. $('#BabylonCanvasContainer-main').css('display','block');
  1936. $('#MultiTouchSurface').css('display','block');
  1937. e.preventDefault();
  1938. }
  1939. }
  1940. }
  1941. });
  1942.  
  1943. }
  1944. }
  1945.  
  1946. async function fetchData() {
  1947. try {
  1948. const response = await fetch("https://gf.qytechs.cn/zh-CN/scripts/455741-xbox-cloud-gaming%E4%BC%98%E5%8C%96%E6%95%B4%E5%90%88/versions");
  1949. const data = await response.text();
  1950.  
  1951. let historyVersion = $(data).find('.history_versions')[0];
  1952. let hli = $($(historyVersion).find('li .version-number > a')[0]);
  1953. let version = hli.text();
  1954. if(nftxboxversion!=version){
  1955. $('head').append('<style>.blink-text{ display:block!important }</style>');
  1956. }
  1957. } catch (error) {
  1958. console.error('Fetch error:', error);
  1959. }
  1960. }
  1961.  
  1962. // 调用异步函数
  1963. $(document).ready(function () {
  1964. setTimeout(function () {
  1965. let popCss = `
  1966.  
  1967. #popSetting {
  1968. width: 76px;
  1969. height: 33px;
  1970. background: #fff;
  1971. position: absolute;
  1972. top: 30%;
  1973. cursor: pointer;
  1974. box-sizing: border-box;
  1975. background-size: 100% 100%;
  1976. overflow: hidden;
  1977. font-family: Arial;
  1978. font-size: 18px;
  1979. line-height: 30px;
  1980. font-weight: bold;
  1981. color: #000000bf;
  1982. border: 2px solid;
  1983. border-radius: 10px;
  1984. -webkit-user-select: none;
  1985. -moz-user-select: none;
  1986. -ms-user-select: none;
  1987. user-select: none ;
  1988. }
  1989. .better-xcloud-hidden {
  1990. visibility: hidden !important;
  1991. }
  1992.  
  1993. .better-xcloud-stats-bar {
  1994. display: block;
  1995. user-select: none;
  1996. position: fixed;
  1997. top: 0;
  1998. background-color: #000;
  1999. color: #fff;
  2000. font-family: Consolas, "Courier New", Courier, monospace;
  2001. font-size: 0.9rem;
  2002. padding-left: 8px;
  2003. z-index: 1000;
  2004. text-wrap: nowrap;
  2005. }
  2006.  
  2007. .better-xcloud-stats-bar[data-position=top-left] {
  2008. left: 20px;
  2009. }
  2010.  
  2011. .better-xcloud-stats-bar[data-position=top-right] {
  2012. right: 0;
  2013. }
  2014.  
  2015. .better-xcloud-stats-bar[data-position=top-center] {
  2016. transform: translate(-50%, 0);
  2017. left: 50%;
  2018. }
  2019.  
  2020. .better-xcloud-stats-bar[data-transparent=true] {
  2021. background: none;
  2022. 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);
  2023. }
  2024.  
  2025. .better-xcloud-stats-bar label {
  2026. margin: 0 8px 0 0;
  2027. font-family: Bahnschrift, Arial, Helvetica, sans-serif;
  2028. font-size: inherit;
  2029. font-weight: bold;
  2030. vertical-align: middle;
  2031. }
  2032.  
  2033. .better-xcloud-stats-bar span {
  2034. min-width: 60px;
  2035. display: inline-block;
  2036. text-align: right;
  2037. padding-right: 8px;
  2038. margin-right: 8px;
  2039. border-right: 2px solid #fff;
  2040. vertical-align: middle;
  2041. }
  2042.  
  2043. .better-xcloud-stats-bar span[data-grade=good] {
  2044. color: #6bffff;
  2045. }
  2046.  
  2047. .better-xcloud-stats-bar span[data-grade=ok] {
  2048. color: #fff16b;
  2049. }
  2050.  
  2051. .better-xcloud-stats-bar span[data-grade=bad] {
  2052. color: #ff5f5f;
  2053. }
  2054.  
  2055. .better-xcloud-stats-bar span:first-of-type {
  2056. min-width: 30px;
  2057. }
  2058.  
  2059. .better-xcloud-stats-bar span:last-of-type {
  2060. border: 0;
  2061. margin-right: 0;
  2062. }
  2063.  
  2064. .better-xcloud-stats-settings {
  2065. display: none;
  2066. position: fixed;
  2067. top: 50%;
  2068. left: 50%;
  2069. margin-right: -50%;
  2070. transform: translate(-50%, -50%);
  2071. width: 420px;
  2072. padding: 20px;
  2073. border-radius: 8px;
  2074. z-index: 1000;
  2075. background: #1a1b1e;
  2076. color: #fff;
  2077. font-weight: 400;
  2078. font-size: 16px;
  2079. font-family: "Segoe UI", Arial, Helvetica, sans-serif;
  2080. box-shadow: 0 0 6px #000;
  2081. user-select: none;
  2082. }
  2083.  
  2084. .better-xcloud-stats-settings *:focus {
  2085. outline: none !important;
  2086. }
  2087.  
  2088. .better-xcloud-stats-settings > b {
  2089. color: #fff;
  2090. display: block;
  2091. font-family: Bahnschrift, Arial, Helvetica, sans-serif;
  2092. font-size: 26px;
  2093. font-weight: 400;
  2094. line-height: 32px;
  2095. margin-bottom: 12px;
  2096. }
  2097.  
  2098. .better-xcloud-stats-settings > div {
  2099. display: flex;
  2100. margin-bottom: 8px;
  2101. padding: 2px 4px;
  2102. }
  2103.  
  2104. .better-xcloud-stats-settings label {
  2105. flex: 1;
  2106. margin-bottom: 0;
  2107. align-self: center;
  2108. }
  2109.  
  2110. .better-xcloud-stats-settings button {
  2111. padding: 8px 32px;
  2112. margin: 20px auto 0;
  2113. border: none;
  2114. border-radius: 4px;
  2115. display: block;
  2116. background-color: #2d3036;
  2117. text-align: center;
  2118. color: white;
  2119. text-transform: uppercase;
  2120. font-family: Bahnschrift, Arial, Helvetica, sans-serif;
  2121. font-weight: 400;
  2122. line-height: 18px;
  2123. font-size: 14px;
  2124. }
  2125.  
  2126. @media (hover: hover) {
  2127. .better-xcloud-stats-settings button:hover {
  2128. background-color: #515863;
  2129. }
  2130. }
  2131.  
  2132. .better-xcloud-stats-settings button:focus {
  2133. background-color: #515863;
  2134. }
  2135.  
  2136. .better-xcloud-gone {
  2137. display: none !important;
  2138. }
  2139.  
  2140. .better-xcloud-quick-settings-bar {
  2141. display: none;
  2142. user-select: none;
  2143. -webkit-user-select: none;
  2144. position: fixed;
  2145. bottom: 0;
  2146. left: 50%;
  2147. transform: translate(-50%, 0);
  2148. z-index: 9999;
  2149. padding: 16px;
  2150. width: 600px;
  2151. background: #1a1b1e;
  2152. color: #fff;
  2153. border-radius: 8px 8px 0 0;
  2154. font-weight: 400;
  2155. font-size: 14px;
  2156. font-family: Bahnschrift, Arial, Helvetica, sans-serif;
  2157. text-align: center;
  2158. box-shadow: 0px 0px 6px #000;
  2159. opacity: 0.95;
  2160. }
  2161.  
  2162. .better-xcloud-quick-settings-bar *:focus {
  2163. outline: none !important;
  2164. }
  2165.  
  2166. .better-xcloud-quick-settings-bar > div {
  2167. flex: 1;
  2168. }
  2169.  
  2170. .better-xcloud-quick-settings-bar label {
  2171. font-size: 16px;
  2172. display: block;
  2173. margin-bottom: 8px;
  2174. }
  2175.  
  2176. .better-xcloud-quick-settings-bar input {
  2177. width: 22px;
  2178. height: 22px;
  2179. }
  2180.  
  2181. .better-xcloud-quick-settings-bar button {
  2182. border: none;
  2183. width: 22px;
  2184. height: 22px;
  2185. margin: 0 4px;
  2186. line-height: 22px;
  2187. background-color: #515151;
  2188. color: #fff;
  2189. border-radius: 4px;
  2190. }
  2191.  
  2192. @media (hover: hover) {
  2193. .better-xcloud-quick-settings-bar button:hover {
  2194. background-color: #414141;
  2195. color: white;
  2196. }
  2197. }
  2198.  
  2199. .better-xcloud-quick-settings-bar button:active {
  2200. background-color: #414141;
  2201. color: white;
  2202. }
  2203.  
  2204. .better-xcloud-quick-settings-bar span {
  2205. display: inline-block;
  2206. width: 40px;
  2207. font-weight: bold;
  2208. font-family: Consolas, "Courier New", Courier, monospace;
  2209. }
  2210.  
  2211.  
  2212. .closeSetting1 {
  2213. color: #0099CC;
  2214. background: transparent;
  2215. border: 2px solid #0099CC;
  2216. border-radius: 6px;
  2217. border: none;
  2218. color: white;
  2219. padding: 3px 13px;
  2220. text-align: center;
  2221. display: inline-block;
  2222. font-size: 16px;
  2223. margin: 4px 2px;
  2224. -webkit-transition-duration: 0.4s; /* Safari */
  2225. transition-duration: 0.4s;
  2226. cursor: pointer;
  2227. text-decoration: none;
  2228. text-transform: uppercase;
  2229. }
  2230. .closeSetting2 {
  2231. background-color: white;
  2232. color: black;
  2233. border: 2px solid #008CBA;
  2234. display: block;
  2235. margin: 0 auto;
  2236. margin-top: 5px;
  2237. }
  2238. .closeSetting2:hover {
  2239. background-color: #008CBA;
  2240. color: white;
  2241. }
  2242. .settingsBackgroud{
  2243. position: fixed;
  2244. left: 0;
  2245. top: 0;
  2246. background: #0000;
  2247. width: 100%;
  2248. height: 100%;
  2249. overflow: scroll;
  2250. z-index:8888;
  2251. }
  2252. .settingsBox{
  2253. position: relative;
  2254. background: wheat;
  2255. width: fit-content;
  2256. height: fit-content;
  2257. border-radius: 5px;
  2258. margin: 5% auto;
  2259. padding: 10px;
  2260. font-family: '微软雅黑';
  2261. line-height: 22px;
  2262. top:5%;
  2263. z-index:8889;
  2264. }
  2265. .settingsBoxInputRadio{
  2266. background-color: initial;
  2267. cursor: pointer;
  2268. appearance: auto;
  2269. box-sizing: border-box;
  2270. margin: 3px 3px 0px 5px;
  2271. padding: initial;
  2272. padding-top: initial;
  2273. padding-right: initial;
  2274. padding-bottom: initial;
  2275. padding-left: initial;
  2276. border: initial;
  2277. -webkit-appearance: checkbox;
  2278. accent-color: dodgerblue;
  2279. }
  2280.  
  2281. #StreamHud >div{
  2282. background-color:rgba(255,0,0,0)!important;
  2283. }
  2284.  
  2285. #StreamHud >button{
  2286. background-color:rgba(0,0,0,0)!important;
  2287. }
  2288. #StreamHud >button > div{
  2289. opacity:0.3!important;
  2290. }
  2291.  
  2292. #touchControllerEventArea {
  2293. pointer-events: auto;
  2294. position: fixed;
  2295. bottom: 0;
  2296. right: 0;
  2297. width: 33%;
  2298. height: 6vh;
  2299. z-index: 5678;
  2300. background-color: rgba(0, 0, 0, 0);
  2301. }
  2302. .better-xcloud-badges {
  2303. margin-top:230px!important;
  2304. left:50px;
  2305. position: absolute;
  2306. margin-left: 0px;
  2307. user-select: none;
  2308. -webkit-user-select: none;
  2309. }
  2310.  
  2311. .better-xcloud-badge {
  2312. border: none;
  2313. display: inline-block;
  2314. line-height: 24px;
  2315. color: #fff;
  2316. font-family: Bahnschrift Semibold, Arial, Helvetica, sans-serif;
  2317. font-size: 14px;
  2318. font-weight: 400;
  2319. margin: 0 8px 8px 0;
  2320. box-shadow: 0px 0px 6px #000;
  2321. border-radius: 4px;
  2322. }
  2323.  
  2324. .better-xcloud-badge-name {
  2325. background-color: #2d3036;
  2326. display: inline-block;
  2327. padding: 2px 8px;
  2328. border-radius: 4px 0 0 4px;
  2329. text-transform: uppercase;
  2330. }
  2331.  
  2332. .better-xcloud-badge-value {
  2333. background-color: grey;
  2334. display: inline-block;
  2335. padding: 2px 8px;
  2336. border-radius: 0 4px 4px 0;
  2337. }
  2338.  
  2339. .better-xcloud-badge-battery[data-charging=true] span:first-of-type::after {
  2340. content: ' ⚡️';
  2341. }
  2342.  
  2343. div[class*=StreamMenu-module__menuContainer] {
  2344. height:75%!important;
  2345. }
  2346.  
  2347. div[class*=NotFocusedDialog-module__container] {
  2348. display:none
  2349. }
  2350. @keyframes blink {
  2351. 20% {color: blueviolet; }
  2352. 50% { color: blue; }
  2353. 100% { color: green; }
  2354. }
  2355.  
  2356. .blink-text {
  2357. font-size: 15px;
  2358. font-weight: bold;
  2359. animation: blink 3s infinite;
  2360. float: right;
  2361. cursor: pointer;
  2362. display:none
  2363. }
  2364. `;
  2365. if (NFTconfig['disableTouchControls'] == 1) {
  2366. popCss += `
  2367. #MultiTouchSurface, #BabylonCanvasContainer-main {
  2368. display: none !important;
  2369. }
  2370.  
  2371. `};
  2372.  
  2373. let xfbasicStyle = document.createElement('style');
  2374. xfbasicStyle.innerHTML = popCss;
  2375. let docxf = document.head || document.documentElement;
  2376. docxf.appendChild(xfbasicStyle);
  2377. if (NFTconfig['noPopSetting'] == 0) {
  2378. $('body').append(`<div id="popSetting" style="display:block">⚙️ 设置</div>`);
  2379. $(document).on('click', '#popSetting', function () {
  2380. naifeitian.showSetting();
  2381. });
  2382. }
  2383.  
  2384.  
  2385. fetchData();
  2386.  
  2387. initSettingBox();
  2388. updateVideoPlayerCss();
  2389. StreamStats.render();
  2390. setupVideoSettingsBar();
  2391. initSlideHide();
  2392. }, 2000);
  2393.  
  2394. });
  2395.  
  2396. let timer;
  2397. let mousehidding = false;
  2398. $(document).mousemove(function () {
  2399. if (mousehidding) {
  2400. mousehidding = false;
  2401. return;
  2402. }
  2403. if (timer) {
  2404. clearTimeout(timer);
  2405. timer = 0;
  2406. }
  2407. $('html').css({
  2408. cursor: ''
  2409. });
  2410. timer = setTimeout(function () {
  2411. mousehidding = true;
  2412. $('html').css({
  2413. cursor: 'none'
  2414. });
  2415. }, 2000);
  2416. });
  2417.  
  2418. $(window).on('popstate', function () {
  2419. exitGame();
  2420. });
  2421.  
  2422. let _pushState = window.history.pushState;
  2423. window.history.pushState = function () {
  2424. if (NFTconfig['noPopSetting'] == 0) {
  2425. if (arguments[2].substring(arguments[2].length, arguments[2].length - 5) == '/play') {
  2426. $('#popSetting').css('display', 'block');
  2427.  
  2428. } else {
  2429. $('#popSetting').css('display', 'none');
  2430. }
  2431. }
  2432. exitGame();
  2433. return _pushState.apply(this, arguments);
  2434. }
  2435.  
  2436. window.onpopstate = function (event) {
  2437. if (event.state) {
  2438. if (window.location.href.slice(-5) == '/play') {
  2439. exitGame();
  2440. }
  2441. }
  2442. };
  2443.  
  2444.  
  2445. RTCPeerConnection.prototype.orgAddIceCandidate = RTCPeerConnection.prototype.addIceCandidate;
  2446. RTCPeerConnection.prototype.addIceCandidate = function (...args) {
  2447.  
  2448.  
  2449. const candidate = args[0].candidate;
  2450. if (candidate && candidate.startsWith('a=candidate:1 ')) {
  2451. STREAM_WEBRTC = this;
  2452. StreamBadges.ipv6 = candidate.substring(20).includes(':');
  2453. }
  2454.  
  2455. STREAM_WEBRTC = this;
  2456. return this.orgAddIceCandidate.apply(this, args);
  2457. }
  2458.  
  2459. function getVideoPlayerFilterStyle() {
  2460. const filters = [];
  2461.  
  2462. const clarity = NFTconfig['VIDEO_CLARITY']['default'];
  2463. if (clarity != 0) {
  2464. const level = 7 - (clarity - 1); // 5,6,7
  2465. const matrix = `0 -1 0 -1 ${level} -1 0 -1 0`;
  2466. document.getElementById('better-xcloud-filter-clarity-matrix').setAttributeNS(null, 'kernelMatrix', matrix);
  2467.  
  2468. filters.push(`url(#better-xcloud-filter-clarity)`);
  2469. }
  2470.  
  2471. const saturation = NFTconfig['VIDEO_SATURATION']['default'];
  2472. if (saturation != 100) {
  2473. filters.push(`saturate(${saturation}%)`);
  2474. }
  2475.  
  2476. const contrast = NFTconfig['VIDEO_CONTRAST']['default'];
  2477. if (contrast != 100) {
  2478. filters.push(`contrast(${contrast}%)`);
  2479. }
  2480.  
  2481. const brightness = NFTconfig['VIDEO_BRIGHTNESS']['default'];
  2482. if (brightness != 100) {
  2483. filters.push(`brightness(${brightness}%)`);
  2484. }
  2485.  
  2486. return filters.join(' ');
  2487. }
  2488.  
  2489.  
  2490. function updateVideoPlayerCss() {
  2491. let $elm = document.getElementById('better-xcloud-video-css');
  2492. if (!$elm) {
  2493. const CE = createElement;
  2494.  
  2495. $elm = CE('style', { id: 'better-xcloud-video-css' });
  2496. document.documentElement.appendChild($elm);
  2497.  
  2498. // Setup SVG filters
  2499. const $svg = CE('svg', {
  2500. 'id': 'better-xcloud-video-filters',
  2501. 'xmlns': 'http://www.w3.org/2000/svg',
  2502. 'class': 'better-xcloud-gone',
  2503. }, CE('defs', { 'xmlns': 'http://www.w3.org/2000/svg' },
  2504. CE('filter', { 'id': 'better-xcloud-filter-clarity', 'xmlns': 'http://www.w3.org/2000/svg' },
  2505. CE('feConvolveMatrix', { 'id': 'better-xcloud-filter-clarity-matrix', 'order': '3', 'xmlns': 'http://www.w3.org/2000/svg' }))
  2506. )
  2507. );
  2508. document.documentElement.appendChild($svg);
  2509. }
  2510.  
  2511. let filters = getVideoPlayerFilterStyle();
  2512. let css = '';
  2513. if (filters) {
  2514. css += `filter: ${filters} !important;`;
  2515. }
  2516.  
  2517. if (NFTconfig['video_stretch'].default == 'fill') {
  2518. css += 'object-fit: fill !important;';
  2519. }
  2520.  
  2521. if (NFTconfig['video_stretch'].default == 'setting') {
  2522. css += `transform: scaleX(` + (NFTconfig['video_stretch_x_y'].x * 1 + 1) + `) scaleY(` + (NFTconfig['video_stretch_x_y'].y * 1 + 1) + `) !important;`;
  2523. }
  2524.  
  2525. if (css) {
  2526. css = `#game-stream video {${css}}`;
  2527. }
  2528.  
  2529. $elm.textContent = css;
  2530. }
  2531. function injectVideoSettingsButton() {
  2532. const $screen = document.querySelector('#PageContent section[class*=PureScreens]');
  2533. if (!$screen) {
  2534. return;
  2535. }
  2536.  
  2537. if ($screen.xObserving) {
  2538. return;
  2539. }
  2540.  
  2541. $screen.xObserving = true;
  2542. const $quickBar = document.querySelector('.better-xcloud-quick-settings-bar');
  2543. const $parent = $screen.parentElement;
  2544. const hideQuickBarFunc = e => {
  2545. e.stopPropagation();
  2546. if (e.target != $parent && e.target.id !== 'MultiTouchSurface' && !e.target.querySelector('#BabylonCanvasContainer-main')) {
  2547. return;
  2548. }
  2549.  
  2550. // Hide Quick settings bar
  2551. $quickBar.style.display = 'none';
  2552.  
  2553. $parent.removeEventListener('click', hideQuickBarFunc);
  2554. $parent.removeEventListener('touchstart', hideQuickBarFunc);
  2555.  
  2556. if (e.target.id === 'MultiTouchSurface') {
  2557. e.target.removeEventListener('touchstart', hideQuickBarFunc);
  2558. }
  2559. }
  2560. const observer = new MutationObserver(mutationList => {
  2561. mutationList.forEach(item => {
  2562. if (item.type !== 'childList') {
  2563. return;
  2564. }
  2565.  
  2566. item.addedNodes.forEach(async node => {
  2567. if (!node.className || !node.className.startsWith('StreamMenu')) {
  2568. return;
  2569. }
  2570.  
  2571. const $orgButton = node.querySelector('div > div > button');
  2572. if (!$orgButton) {
  2573. return;
  2574. }
  2575.  
  2576. // Create Video Settings button
  2577. const $btnVideoSettings = cloneStreamMenuButton($orgButton, '视频调整', ICON_VIDEO_SETTINGS);
  2578. $btnVideoSettings.addEventListener('click', e => {
  2579. e.preventDefault();
  2580. e.stopPropagation();
  2581.  
  2582. // Close HUD
  2583. $btnCloseHud.click();
  2584.  
  2585. // Show Quick settings bar
  2586. $quickBar.style.display = 'flex';
  2587.  
  2588. $parent.addEventListener('click', hideQuickBarFunc);
  2589. $parent.addEventListener('touchstart', hideQuickBarFunc);
  2590.  
  2591. const $touchSurface = document.getElementById('MultiTouchSurface');
  2592. $touchSurface && $touchSurface.style.display != 'none' && $touchSurface.addEventListener('touchstart', hideQuickBarFunc);
  2593. });
  2594. // Add button at the beginning
  2595. $orgButton.parentElement.insertBefore($btnVideoSettings, $orgButton.parentElement.firstChild);
  2596.  
  2597. // Hide Quick bar when closing HUD
  2598. const $btnCloseHud = document.querySelector('button[class*=StreamMenu-module__backButton]');
  2599. $btnCloseHud.addEventListener('click', e => {
  2600. $quickBar.style.display = 'none';
  2601. });
  2602.  
  2603. // Create Stream Stats button
  2604. const $btnStreamStats = cloneStreamMenuButton($orgButton, '流监控', ICON_STREAM_STATS);
  2605. $btnStreamStats.addEventListener('click', e => {
  2606. e.preventDefault();
  2607. e.stopPropagation();
  2608.  
  2609. // Toggle Stream Stats
  2610. StreamStats.toggle();
  2611. });
  2612.  
  2613. // Insert after Video Settings button
  2614. $orgButton.parentElement.insertBefore($btnStreamStats, $btnVideoSettings);
  2615.  
  2616. //桥
  2617.  
  2618. const $menu = document.querySelector('div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]');
  2619. const streamBadgesElement = await StreamBadges.render();
  2620.  
  2621. $menu.insertAdjacentElement('afterend', streamBadgesElement);
  2622.  
  2623. });
  2624. });
  2625. });
  2626. observer.observe($screen, { subtree: true, childList: true });
  2627. }
  2628.  
  2629. let transitionComplete = true;
  2630. let SildeopenBlock=0;
  2631. function patchVideoApi() {
  2632.  
  2633. // Show video player when it's ready
  2634. let showFunc;
  2635. showFunc = function () {
  2636.  
  2637. this.removeEventListener('playing', showFunc);
  2638.  
  2639. if (!this.videoWidth) {
  2640. return;
  2641. }
  2642.  
  2643. onStreamStarted(this);
  2644. STREAM_WEBRTC?.getStats().then(stats => {
  2645.  
  2646. if (NFTconfig['STATS_SHOW_WHEN_PLAYING']['default']) {
  2647. StreamStats.start();
  2648. }
  2649. });
  2650. let chkDom=setInterval(()=>{
  2651. if($('#StreamHud button').length>0){
  2652. clearInterval(chkDom);
  2653.  
  2654. if($('#StreamHud').css('left')=='0px' && NFTconfig['STATS_SLIDE_OPEN']['default']){
  2655. StreamStats.start();
  2656. }
  2657. if (SildeopenBlock == 0) {
  2658. SildeopenBlock=1;
  2659. $(document).on('click', '.'+ $($('#StreamHud button')[$('#StreamHud button').length-1]).attr('class'), function () {
  2660. transitionComplete=true;
  2661. });
  2662.  
  2663. $(document).on('transitionend', '#StreamHud', function () {
  2664. if(!NFTconfig['STATS_SLIDE_OPEN']['default']){return;}
  2665. if(!transitionComplete){return;}
  2666. if($('#StreamHud').css('left')=='0px'){
  2667. StreamStats.start();
  2668. }else{
  2669. StreamStats.stop();
  2670. }
  2671. transitionComplete=false;
  2672. });
  2673. }
  2674.  
  2675. }
  2676. },2000);
  2677. }
  2678. HTMLMediaElement.prototype.orgPlay = HTMLMediaElement.prototype.play;
  2679. HTMLMediaElement.prototype.play = function () {
  2680. if (letmeOb && NFTconfig['antiKick'] == 1) {
  2681. const divElement = $('div[data-testid="ui-container"]')[0];
  2682. const observer = new MutationObserver(function (mutations) {
  2683. try {
  2684. mutations.forEach(function (mutation) {
  2685. if (mutation.type === 'childList') {
  2686. mutation.addedNodes.forEach(function (addedNode) {
  2687. let btn = $(addedNode).find('button[data-auto-focus="true"]');
  2688. if ($(btn).length > 0 && btn.parent().children().length == 1) {
  2689. $(btn).click();
  2690. throw new Error("巴啦啦能量-呼尼拉-魔仙变身!");
  2691. }
  2692. });
  2693. }
  2694. });
  2695. } catch (e) { }
  2696. });
  2697.  
  2698. setTimeout(() => {
  2699. observer.observe(divElement, { childList: true, subtree: true });
  2700. console.log('antiKick已部署');
  2701. }, 1000 * 20);
  2702. letmeOb = false;
  2703. }
  2704.  
  2705. this.addEventListener('playing', showFunc);
  2706. injectVideoSettingsButton();
  2707. return this.orgPlay.apply(this);
  2708. };
  2709.  
  2710.  
  2711. }
  2712.  
  2713. function onStreamStarted($video) {
  2714.  
  2715. StreamBadges.resolution = {width: $video.videoWidth, height: $video.videoHeight};
  2716. StreamBadges.startTimestamp = +new Date;
  2717.  
  2718. // Get battery level
  2719. try {
  2720. navigator.getBattery && navigator.getBattery().then(bm => {
  2721. StreamBadges.startBatteryLevel = Math.round(bm.level * 100);
  2722. });
  2723. } catch(e) {}
  2724.  
  2725. STREAM_WEBRTC.getStats().then(stats => {
  2726. const allVideoCodecs = {};
  2727. let videoCodecId;
  2728.  
  2729. const allAudioCodecs = {};
  2730. let audioCodecId;
  2731.  
  2732. stats.forEach(stat => {
  2733. if (stat.type == 'codec') {
  2734. const mimeType = stat.mimeType.split('/');
  2735. if (mimeType[0] === 'video') {
  2736. // Store all video stats
  2737. allVideoCodecs[stat.id] = stat;
  2738. } else if (mimeType[0] === 'audio') {
  2739. // Store all audio stats
  2740. allAudioCodecs[stat.id] = stat;
  2741. }
  2742. } else if (stat.type === 'inbound-rtp' && stat.packetsReceived > 0) {
  2743. // Get the codecId of the video/audio track currently being used
  2744. if (stat.kind === 'video') {
  2745. videoCodecId = stat.codecId;
  2746. } else if (stat.kind === 'audio') {
  2747. audioCodecId = stat.codecId;
  2748. }
  2749. }
  2750. });
  2751.  
  2752. // Get video codec from codecId
  2753. if (videoCodecId) {
  2754. const videoStat = allVideoCodecs[videoCodecId];
  2755. const video = {
  2756. codec: videoStat.mimeType.substring(6),
  2757. };
  2758.  
  2759. if (video.codec === 'H264') {
  2760. const match = /profile-level-id=([0-9a-f]{6})/.exec(videoStat.sdpFmtpLine);
  2761. video.profile = match ? match[1] : null;
  2762. }
  2763.  
  2764. StreamBadges.video = video;
  2765. }
  2766.  
  2767. // Get audio codec from codecId
  2768. if (audioCodecId) {
  2769. const audioStat = allAudioCodecs[audioCodecId];
  2770. StreamBadges.audio = {
  2771. codec: audioStat.mimeType.substring(6),
  2772. bitrate: audioStat.clockRate,
  2773. }
  2774. }
  2775.  
  2776. });
  2777.  
  2778.  
  2779. }
  2780. function moveCodecToIndex(array, currentIndex, targetIndex, element) {
  2781. array.splice(currentIndex, 1);
  2782. array.splice(targetIndex, 0, element);
  2783. }
  2784. function customizeRtcCodecs() {
  2785. const customCodecProfile = NFTconfig['video_quality']['default'];
  2786.  
  2787. if (customCodecProfile === '默认') {
  2788. return;
  2789. }
  2790. if (typeof RTCRtpTransceiver === 'undefined' || !('setCodecPreferences' in RTCRtpTransceiver.prototype)) {
  2791. return false;
  2792. }
  2793.  
  2794. let codecProfilePrefix="";
  2795. let codecProfileLevelId = "";
  2796. let codecMimeType="";
  2797. const codecProfileMap = {"264": {"高": "4d","中": "42e","低": "420"}};
  2798.  
  2799. if (customCodecProfile.includes("264")) {
  2800. const codecLevel = Object.keys(codecProfileMap["264"]).find(level => customCodecProfile.includes(level));
  2801. if (codecLevel) {
  2802. codecProfilePrefix = codecProfileMap["264"][codecLevel];
  2803. codecProfileLevelId = `profile-level-id=${codecProfilePrefix}`;
  2804. }
  2805. }else{
  2806. codecMimeType="video/"+customCodecProfile;
  2807. }
  2808.  
  2809. RTCRtpTransceiver.prototype.originalSetCodecPreferences = RTCRtpTransceiver.prototype.setCodecPreferences;
  2810. RTCRtpTransceiver.prototype.setCodecPreferences = function(codecs) {
  2811. const customizedCodecs = codecs.slice();
  2812. let insertionIndex = 0;
  2813.  
  2814. customizedCodecs.forEach((codec, index) => {
  2815. if (codecProfileLevelId !== '' && codec.sdpFmtpLine && codec.sdpFmtpLine.includes(codecProfileLevelId)) {
  2816. moveCodecToIndex(customizedCodecs, index, insertionIndex, codec);
  2817. insertionIndex++;
  2818. } else if (codec.mimeType === codecMimeType) {
  2819. moveCodecToIndex(customizedCodecs, index, insertionIndex, codec);
  2820. insertionIndex++;
  2821. }
  2822. });
  2823.  
  2824. try {
  2825. debugger
  2826. this.originalSetCodecPreferences.apply(this, [customizedCodecs]);
  2827. console.log("编解码偏好配置成功");
  2828. } catch (error) {
  2829. console.log("无法修改编解码配置,将使用默认设置");
  2830. this.originalSetCodecPreferences.apply(this, [codecs]);
  2831. }
  2832. }
  2833. }
  2834.  
  2835. customizeRtcCodecs();
  2836. patchVideoApi();
  2837.  
  2838. let mslogotimeOut = 0;
  2839. function mslogoClickevent(mslogoInterval, s) {
  2840. let mslogodom = $($('header>div>div>button')[1]);
  2841. if (mslogodom.length > 0) {
  2842. clearInterval(mslogoInterval);
  2843. mslogodom = mslogodom.next();
  2844. if (mslogodom.text() == ("⚙️ 设置" + nftxboxversion)) { return; }
  2845. mslogodom.removeAttr('href');
  2846. mslogodom.css("color", 'white');
  2847. mslogodom.text("⚙️ 设置" + nftxboxversion);
  2848. mslogodom.click(() => {
  2849. naifeitian.showSetting();
  2850. });
  2851. setTimeout(() => { mslogoClickevent(mslogoInterval) }, 5000);
  2852. }
  2853. mslogotimeOut = mslogotimeOut + 1;
  2854. if (mslogotimeOut > 10) {
  2855. mslogotimeOut = 0;
  2856. clearInterval(mslogoInterval);
  2857. }
  2858. }
  2859. let mslogoInterval = setInterval(() => {
  2860. mslogoClickevent(mslogoInterval, 3000);
  2861. }, 1000);
  2862.  
  2863. function bindmslogoevent() {
  2864. let divElement = $('#gamepass-root > div > div');
  2865. if (divElement.length < 1) {
  2866. setTimeout(() => {
  2867. bindmslogoevent();
  2868. }, 2333);
  2869. return;
  2870. }
  2871. divElement = divElement.get(0);
  2872. let mslogodom = $(divElement).children('header').find('a[href]');
  2873. if (mslogodom.length > 1) { mslogodom = $(mslogodom.get(0)); }
  2874. if (mslogodom.text() == ("⚙️ 设置" + nftxboxversion)) { return; }
  2875. mslogodom.removeAttr('href');
  2876. mslogodom.css("color", 'white');
  2877. mslogodom.text("⚙️ 设置" + nftxboxversion);
  2878. mslogodom.click(() => {
  2879. naifeitian.showSetting();
  2880. });
  2881. setTimeout(() => { bindmslogoevent() }, 5000);
  2882. }
  2883.  
  2884. bindmslogoevent();
  2885.  
  2886. if (window.location.pathname.toLocaleLowerCase() == '/zh-cn/play') {
  2887. window.location.href = "https://www.xbox.com/en-us/play";
  2888. }
  2889.  
  2890. console.log("all done");
  2891. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址