// ==UserScript== // @name Xbox CLoud Gaming Enhancement // @namespace https://b1ue.me // @description Xbox CLoud Gaming优化整合 扩展脚本.用以支持原脚本没有覆盖到的部分功能,比如 进入全屏模式时自动横屏/游戏信息汉化/一些游戏的本地多人合作 // @version 1.0.0 // @author b1ue // @license MIT // @match https://www.xbox.com/*/*play* // @run-at document-start // @grant GM.xmlHttpRequest // @grant unsafeWindow // @connect update.greasyfork.org // @require https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.4.1/jquery.min.js // @downloadURL none // ==/UserScript== (function() { 'use strict'; const Nconfig = { localizeGameInfo: 1, supportLocalCoOp: 0, alwaysShowTitle: 2, no_need_VPN_play: 0, enableRemotePlay: 0, }; Object.keys(Nconfig).forEach(key => { let _val = localStorage.getItem(key + 'GM'); try { _val = JSON.parse(_val) ;} catch (e) {} if(_val != null) Nconfig[key] = _val; }); let game_titles = {}; (async () => { const timestamp = () => Math.floor(new Date().getTime() / 1000); let resText = localStorage.getItem('game_titles_GM'); const game_titles_gettime = localStorage.getItem("game_titles_gettime_GM") || 0; if(!resText || timestamp() - game_titles_gettime > 7200){ const r = await GM.xmlHttpRequest({url: "https://update.greasyfork.org/scripts/493376/xbt-title.js", nocache:true}); if(r.status == 200){ resText = r.responseText; localStorage.setItem("game_titles_GM", resText); localStorage.setItem("game_titles_gettime_GM", timestamp()); } } game_titles = JSON.parse(resText); })(); let allFullLanguages = []; let browserFirstLanguage = "zh-CN"; navigator.languages.forEach(language => { const reg = /^[a-z]{2}-[A-Z]{2}$/; const isFullLanguage = reg.test(language); if (isFullLanguage) allFullLanguages.push(language); }); if (allFullLanguages.length > 0) { browserFirstLanguage = allFullLanguages[0]; } const oWindow = self.unsafeWindow || window; document.addEventListener("fullscreenchange", function (e) { if (document.fullscreenElement) { try { screen?.orientation?.lock("landscape"); } catch (e) {} } }); let checkIpsuc = 0; const originFetch = oWindow.fetch; oWindow.fetch = async (...arg) => { let arg0 = arg[0]; let url = ""; let isRequest = false; switch (typeof arg0) { case "object": url = arg0.url; isRequest = true; break; case "string": url = arg0; break; default: break; } if (!url.includes('xhome.') && url.indexOf('/v2/login/user') > -1) {//xgpuweb.gssv-play-prod.xboxlive.com if(checkIpsuc === 0){ checkIpsuc = 1; let res = originFetch(...arg).catch(error => { checkIpsuc = -1; let remain_count = 5; const check_state = () => { let oTitle = $("[class*='UnsupportedMarketPage-module__title']:visible")[0]; if(oTitle){ if(remain_count > 0){ oTitle.innerText = (Nconfig.no_need_VPN_play==1?'免代理失败':'访问错误') + ',页面将在' + (remain_count--) + '秒后刷新'; setTimeout(check_state, 1000); } else { location.reload(); } } }; setTimeout(check_state, 5000); }); return res; } } if (url === 'https://greasyfork.org/zh-CN/scripts/455741-xbox-cloud-gaming%E4%BC%98%E5%8C%96%E6%95%B4%E5%90%88/versions') { let res = originFetch('https://greasyfork.org/scripts/455741/versions.json').then(response => { response.text = () => response.clone().json().then(json => { const version = json?.[0]?.version; const fake_html = ``; return Promise.resolve(fake_html); }); return response; }); return res; } if (url === 'https://xhome.gssv-play-prod.xboxlive.com/v2/login/user') { if(Nconfig.enableRemotePlay === 0){ return new Promise(()=>{}); //Promise.reject("未开启串流功能,过滤多余请求"); } } if(Nconfig.localizeGameInfo != 1) return originFetch(...arg); if (url.includes('/v3/products')) { let ourl = new URL(url); let json = await arg0.json(); let body = JSON.stringify(json); ourl.searchParams.set("language",browserFirstLanguage); let nurl = ourl.toString(); arg[0] = new Request(nurl, { method: arg0.method, headers: arg0.headers, body: body, }); let res = originFetch(...arg).then(response => { response.json = () => response.clone().json().then(json => { for(let gId in json.Products){ let title_zh = ""; if(gId in game_titles && (title_zh = game_titles[gId][0])) json.Products[gId].ProductTitle = title_zh; } return Promise.resolve(json); }); return response; }); return res; } else if (url.includes('/search/v2')) { let ourl = new URL(url); let json = await arg0.json(); let body = JSON.stringify(json); ourl.searchParams.set("language",browserFirstLanguage); let nurl = ourl.toString(); arg[0] = new Request(nurl, { method: arg0.method, headers: arg0.headers, body: body, }); const query = json.Query; const Scope = json.Scope; if(query && Scope === 'EDGEWATER'){ let new_SearchResults = [] for(let gId in game_titles){ if(game_titles[gId][0].includes(query) || game_titles[gId][1].includes(query)){ new_SearchResults.push(gId); } } let res = originFetch(...arg).then(response => { response.json = () => response.clone().json().then(async json => { new_SearchResults = new_SearchResults.filter(gId => !(gId in json.SearchResults)); if(new_SearchResults.length > 0){ const response = await originFetch(`https://catalog.gamepass.com/v3/products?market=${ourl.searchParams.get("market")}&language=${browserFirstLanguage}&hydration=${ourl.searchParams.get("hydration")}`, { method: 'POST', headers: arg0.headers, body: JSON.stringify({ Products: new_SearchResults, }), }); const data = await response.json(); for(let gId in data.Products){ json.Products[gId] = data.Products[gId]; } } for(let gId in json.Products){ let title_zh = ""; if(gId in game_titles && (title_zh = game_titles[gId][0])) json.Products[gId].ProductTitle = title_zh; } json.SearchResults = json.SearchResults.concat(new_SearchResults); return Promise.resolve(json); }); return response; }); return res; } } else if (url.includes('/v4/api/selection')) { let res = originFetch(...arg).then(response => { response.json = () => response.clone().json().then(json => { let items_array = json?.batchrsp?.items; if(items_array){ items_array.forEach( _item => { const item = JSON.parse(_item?.item); const title = item?.ad?.items?.[0]?.title; const actionLink = item?.ad?.items?.[0]?.actionLink; const gId = /msgamepass:\/\/details\?id=([A-Z0-9]+)/.exec(actionLink)?.[1] if(title && gId){ let title_zh = ""; if(gId in game_titles && (title_zh = game_titles[gId][0])){ item.ad.items[0].title = title_zh; _item.item = JSON.stringify(item); } } }); } return Promise.resolve(json); }); return response; }); return res; } return originFetch(...arg); } if(Nconfig.supportLocalCoOp == 1){ const native_includes = String.prototype.includes; String.prototype.includes = function(){ let funcStr = this; const text = 'this.gamepadMappingsToSend=[],'; if (native_includes.call(funcStr, text)){ String.prototype.includes = native_includes; let native_replace = String.prototype.replace; String.prototype.replace = function(){ let funcStr = this; const text = 'this.gamepadMappingsToSend=[],'; if (native_includes.call(funcStr, text)){ String.prototype.replace = native_replace; const patchFunc = () => { let match; let onGamepadChangedStr = this.onGamepadChanged.toString(); // match = onGamepadChangedStr.match(/onGamepadChanged\((?\w+),(?\w+),(?\w+)\)/); onGamepadChangedStr = onGamepadChangedStr.replaceAll('0', 'arguments[1]'); eval(`this.onGamepadChanged = function ${onGamepadChangedStr}`); let onGamepadInputStr = this.onGamepadInput.toString(); match = onGamepadInputStr.match(/(\w+\.GamepadIndex)/); if (match) { const gamepadIndexVar = match[0]; onGamepadInputStr = onGamepadInputStr.replace('this.gamepadStates.get(', `this.gamepadStates.get(${gamepadIndexVar},`); eval(`this.onGamepadInput = function ${onGamepadInputStr}`); console.log('✅ Successfully patched local co-op support'); } else { console.log('❌ Unable to patch local co-op support'); } } let patchFuncStr = patchFunc.toString(); patchFuncStr = patchFuncStr.substring(7, patchFuncStr.length - 1); const newCode = `true; ${patchFuncStr}; true,`; funcStr = funcStr.replace(text, text + newCode); console.log(`应用 本地合作模式 修补`); } return native_replace.apply(funcStr, arguments); } return true; } return native_includes.apply(funcStr, arguments); } } const settingsConfig = [ { label: '游戏信息汉化:', type: 'radio', name: 'localizeGameInfo', display: 'block', options: [ { value: 1, text: '开', id: 'localizeGameInfoOn' }, { value: 0, text: '关', id: 'localizeGameInfoOff' } ], checkedValue: Nconfig.localizeGameInfo, needHr: true }, { label: '本地合作支持:', type: 'radio', name: 'supportLocalCoOp', display: 'block', options: [ { value: 1, text: '开', id: 'supportLocalCoOpOn' }, { value: 0, text: '关', id: 'supportLocalCoOpOff' } ], checkedValue: Nconfig.supportLocalCoOp, needHr: true }, { label: '保持标题显示:', showLable: true, type: 'dropdown', name: 'alwaysShowTitle', display: 'block', options: [ { value: 0, text: '关闭'}, { value: 1, text: '开启'}, { value: 2, text: '仅移动设备'} ], selectedValue: Nconfig.alwaysShowTitle, ignoreChange: true, needHr: true }, ] // 函数用于生成单个设置项的HTML function generateSettingElement(setting) { let settingHTML = ``; if (setting.type === 'radio') { if (setting.options != undefined) { settingHTML += `'; } else if (setting.type === 'text') { settingHTML += ``; } else if (setting.type === 'dropdown') { if (setting.showLable == true) { settingHTML += ``; if (setting.needHr) { settingHTML += `
` } return settingHTML; } function initSettingBox(oSettingBox){ let needrefresh = 0; let settingsHTML = ''; settingsConfig.forEach(setting => { settingsHTML += generateSettingElement(setting); }); $(oSettingBox).children('button.closeSetting1').before(settingsHTML); $(oSettingBox).find('span.blink-text:contains("更新咯~")').attr('onclick','window.open("https://greasyfork.org/zh-CN/scripts/455741");'); $(oSettingBox).find('a[href]').attr('target','_blank'); $('.closeSetting1').click(function() { $(oSettingBox).parent().css('display', 'none'); $('body').css('overflow', 'visible'); if(getconfstring() == origin_config){ event.cancelBubble = true; event.stopPropagation(); return false; } if (needrefresh == 1) history.go(0); }); $(document).on('click', '.localizeGameInfoListener', function () { needrefresh = 1; localStorage.setItem("localizeGameInfoGM", $(this).val()); $('.closeSetting1').text('确定'); }); $(document).on('click', '.supportLocalCoOpListener', function () { needrefresh = 1; localStorage.setItem("supportLocalCoOpGM", $(this).val()); $('.closeSetting1').text('确定'); }); $(document).on('change', '.alwaysShowTitleListener', function () { Nconfig.alwaysShowTitle = parseInt($(this).val()); localStorage.setItem("alwaysShowTitleGM", $(this).val()); toggleTitleVisible(); }); $('#popSetting').css('position','fixed'); $(oSettingBox).parent().css('height','100%'); const getconfstring = () =>{ let text = ''; $(oSettingBox).find('input[type="text"],input[type="checkbox"]:checked,input[type="radio"]:checked,select').each((i,o) => { if(!$(o).hasClass('ignore-change')) text += $(o).val() }); return text; }; let origin_config = getconfstring(); const mutation = new MutationObserver(function(mutationRecoards, observer) { if(mutationRecoards[0].target.innerText == '确定'){ mutationRecoards[0].target.innerText = (getconfstring() == origin_config)?'关闭':'刷新'; } }) mutation.observe($(oSettingBox).find('.closeSetting1')[0], { characterData: true, childList: true }); } let checkSettingBox_Interval = setInterval(() => { let oSettingBox; if(oSettingBox = document.querySelector('#settingsBackgroud .settingsBox')){ clearInterval(checkSettingBox_Interval); initSettingBox(oSettingBox); } },500); function toggleTitleVisible(){ let action = 0; switch(Nconfig.alwaysShowTitle){ case 0: action = 1; break case 1: action = 2; break case 2: action = ('ontouchstart' in window || navigator.msMaxTouchPoints > 0)?2:1; break } if(action == 1){ document.querySelector('style#showTitle')?.remove(); }else if(action == 2){ if(document.querySelector('style#showTitle')) return; const nCss = ` [class^="GameCard-module__gameTitleInnerWrapper___"] { max-height: 100%; visibility: visible; } [class^="GameCard-module__children___"] { visibility: hidden; }` const xfextraStyle = document.createElement('style'); xfextraStyle.id = 'showTitle'; xfextraStyle.innerHTML = nCss; const docxf = document.head || document.documentElement; docxf.appendChild(xfextraStyle); } } $(document).ready(function () { setTimeout(() => { if (checkIpsuc < 0) return; let oTitle = $("[class*='UnsupportedMarketPage-module__title']:visible")[0]; if (oTitle) oTitle.innerText = "如果长时间停留在本页,请尝试刷新"; }, 5000); setTimeout(() => {toggleTitleVisible()},1000); }); const go_origin = history.go; history.go = (arg) => { return go_origin.call(history, arg); }; const setInterval_origin = oWindow.setInterval; oWindow.setInterval = (func, interval) => { let funcStr = func.toString(); if(funcStr.includes('if (checkIpsuc)')){ oWindow.setInterval = setInterval_origin; return; } return setInterval_origin(func, interval); }; let __PRELOADED_STATE__; Object.defineProperty(oWindow, '__PRELOADED_STATE__', { configurable: true, get: () => { return __PRELOADED_STATE__; }, set: state => { state.appContext.marketInfo.locale = browserFirstLanguage; __PRELOADED_STATE__ = state; } }); })();