mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-01 11:51:43 +02:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
df2af43c64 | |||
fca3bee6dd | |||
9bf8a2ef66 | |||
b1df189c7d | |||
d91fdb798e | |||
a291443d43 | |||
8a7be5d523 | |||
7588f37472 | |||
a597d52585 | |||
f945a3adde | |||
438afe086a | |||
f6ee79770c | |||
f36c77e727 |
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Better xCloud
|
// @name Better xCloud
|
||||||
// @namespace https://github.com/redphx
|
// @namespace https://github.com/redphx
|
||||||
// @version 4.1.0
|
// @version 4.1.1
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
787
dist/better-xcloud.user.js
vendored
787
dist/better-xcloud.user.js
vendored
@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Better xCloud
|
// @name Better xCloud
|
||||||
// @namespace https://github.com/redphx
|
// @namespace https://github.com/redphx
|
||||||
// @version 4.1.0
|
// @version 4.1.1
|
||||||
// @description Improve Xbox Cloud Gaming (xCloud) experience
|
// @description Improve Xbox Cloud Gaming (xCloud) experience
|
||||||
// @author redphx
|
// @author redphx
|
||||||
// @license MIT
|
// @license MIT
|
||||||
@ -14,7 +14,7 @@
|
|||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
'use strict';
|
'use strict';
|
||||||
// src/utils/global.ts
|
// src/utils/global.ts
|
||||||
var SCRIPT_VERSION = "4.1.0";
|
var SCRIPT_VERSION = "4.1.1";
|
||||||
var SCRIPT_HOME = "https://github.com/redphx/better-xcloud";
|
var SCRIPT_HOME = "https://github.com/redphx/better-xcloud";
|
||||||
var AppInterface = window.AppInterface;
|
var AppInterface = window.AppInterface;
|
||||||
var STATES = {
|
var STATES = {
|
||||||
@ -3585,7 +3585,9 @@ var UserAgentProfile;
|
|||||||
(function(UserAgentProfile2) {
|
(function(UserAgentProfile2) {
|
||||||
UserAgentProfile2["EDGE_WINDOWS"] = "edge-windows";
|
UserAgentProfile2["EDGE_WINDOWS"] = "edge-windows";
|
||||||
UserAgentProfile2["SAFARI_MACOS"] = "safari-macos";
|
UserAgentProfile2["SAFARI_MACOS"] = "safari-macos";
|
||||||
|
UserAgentProfile2["SMARTTV"] = "smarttv";
|
||||||
UserAgentProfile2["SMARTTV_TIZEN"] = "smarttv-tizen";
|
UserAgentProfile2["SMARTTV_TIZEN"] = "smarttv-tizen";
|
||||||
|
UserAgentProfile2["VR_OCULUS"] = "vr-oculus";
|
||||||
UserAgentProfile2["KIWI_V123"] = "kiwi-v123";
|
UserAgentProfile2["KIWI_V123"] = "kiwi-v123";
|
||||||
UserAgentProfile2["DEFAULT"] = "default";
|
UserAgentProfile2["DEFAULT"] = "default";
|
||||||
UserAgentProfile2["CUSTOM"] = "custom";
|
UserAgentProfile2["CUSTOM"] = "custom";
|
||||||
@ -3604,7 +3606,9 @@ class UserAgent {
|
|||||||
static #USER_AGENTS = {
|
static #USER_AGENTS = {
|
||||||
[UserAgentProfile.EDGE_WINDOWS]: EDGE_USER_AGENT,
|
[UserAgentProfile.EDGE_WINDOWS]: EDGE_USER_AGENT,
|
||||||
[UserAgentProfile.SAFARI_MACOS]: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1",
|
[UserAgentProfile.SAFARI_MACOS]: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1",
|
||||||
|
[UserAgentProfile.SMARTTV]: window.navigator.userAgent + " SmartTV",
|
||||||
[UserAgentProfile.SMARTTV_TIZEN]: "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",
|
[UserAgentProfile.SMARTTV_TIZEN]: "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",
|
||||||
|
[UserAgentProfile.VR_OCULUS]: window.navigator.userAgent + " OculusBrowser VR",
|
||||||
[UserAgentProfile.KIWI_V123]: "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.118 Mobile Safari/537.36"
|
[UserAgentProfile.KIWI_V123]: "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.118 Mobile Safari/537.36"
|
||||||
};
|
};
|
||||||
static getDefault() {
|
static getDefault() {
|
||||||
@ -4459,6 +4463,7 @@ class Preferences {
|
|||||||
default: "default",
|
default: "default",
|
||||||
options: {
|
options: {
|
||||||
default: t("default"),
|
default: t("default"),
|
||||||
|
normal: t("normal"),
|
||||||
tv: t("smart-tv")
|
tv: t("smart-tv")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -4481,7 +4486,9 @@ class Preferences {
|
|||||||
[UserAgentProfile.DEFAULT]: t("default"),
|
[UserAgentProfile.DEFAULT]: t("default"),
|
||||||
[UserAgentProfile.EDGE_WINDOWS]: "Edge + Windows",
|
[UserAgentProfile.EDGE_WINDOWS]: "Edge + Windows",
|
||||||
[UserAgentProfile.SAFARI_MACOS]: "Safari + macOS",
|
[UserAgentProfile.SAFARI_MACOS]: "Safari + macOS",
|
||||||
|
[UserAgentProfile.SMARTTV]: "Smart TV",
|
||||||
[UserAgentProfile.SMARTTV_TIZEN]: "Samsung Smart TV",
|
[UserAgentProfile.SMARTTV_TIZEN]: "Samsung Smart TV",
|
||||||
|
[UserAgentProfile.VR_OCULUS]: "Meta Quest VR",
|
||||||
[UserAgentProfile.KIWI_V123]: "Kiwi Browser v123",
|
[UserAgentProfile.KIWI_V123]: "Kiwi Browser v123",
|
||||||
[UserAgentProfile.CUSTOM]: t("custom")
|
[UserAgentProfile.CUSTOM]: t("custom")
|
||||||
}
|
}
|
||||||
@ -4797,7 +4804,7 @@ var BxExposed = {
|
|||||||
supportedInputTypes = supportedInputTypes.filter((i) => i !== InputType.CUSTOM_TOUCH_OVERLAY && i !== InputType.GENERIC_TOUCH);
|
supportedInputTypes = supportedInputTypes.filter((i) => i !== InputType.CUSTOM_TOUCH_OVERLAY && i !== InputType.GENERIC_TOUCH);
|
||||||
}
|
}
|
||||||
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
|
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
|
||||||
titleInfo.details.hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) && !supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) && !supportedInputTypes.includes(InputType.GENERIC_TOUCH);
|
titleInfo.details.hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) || supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) || supportedInputTypes.includes(InputType.GENERIC_TOUCH);
|
||||||
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === "all") {
|
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === "all") {
|
||||||
titleInfo.details.hasFakeTouchSupport = true;
|
titleInfo.details.hasFakeTouchSupport = true;
|
||||||
supportedInputTypes.push(InputType.GENERIC_TOUCH);
|
supportedInputTypes.push(InputType.GENERIC_TOUCH);
|
||||||
@ -4987,6 +4994,9 @@ var new_default = "<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#
|
|||||||
// src/assets/svg/question.svg
|
// src/assets/svg/question.svg
|
||||||
var question_default = "<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='4' viewBox='0 0 32 32'>\n <g transform='matrix(.256867 0 0 .256867 -16.878964 -18.049342)'><circle cx='128' cy='180' r='12' fill='#fff'/><path d='M128 144v-8c17.67 0 32-12.54 32-28s-14.33-28-32-28-32 12.54-32 28v4' fill='none' stroke='#fff' stroke-width='16'/></g>\n</svg>\n";
|
var question_default = "<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='4' viewBox='0 0 32 32'>\n <g transform='matrix(.256867 0 0 .256867 -16.878964 -18.049342)'><circle cx='128' cy='180' r='12' fill='#fff'/><path d='M128 144v-8c17.67 0 32-12.54 32-28s-14.33-28-32-28-32 12.54-32 28v4' fill='none' stroke='#fff' stroke-width='16'/></g>\n</svg>\n";
|
||||||
|
|
||||||
|
// src/assets/svg/refresh.svg
|
||||||
|
var refresh_default = "<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>\n <path d=\"M23.247 12.377h7.247V5.13\"/><path d=\"M23.911 25.663a13.29 13.29 0 0 1-9.119 3.623C7.504 29.286 1.506 23.289 1.506 16S7.504 2.713 14.792 2.713a13.29 13.29 0 0 1 9.395 3.891l6.307 5.772\"/>\n</svg>\n";
|
||||||
|
|
||||||
// src/assets/svg/remote-play.svg
|
// src/assets/svg/remote-play.svg
|
||||||
var remote_play_default = "<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='4' viewBox='0 0 32 32'>\n <g transform='matrix(.492308 0 0 .581818 -14.7692 -11.6364)'><clipPath id='A'><path d='M30 20h65v55H30z'/></clipPath><g clip-path='url(#A)'><g transform='matrix(.395211 0 0 .334409 11.913 7.01124)'><g transform='matrix(.555556 0 0 .555556 57.8889 -20.2417)' fill='none' stroke='#fff' stroke-width='13.88'><path d='M200 140.564c-42.045-33.285-101.955-33.285-144 0M168 165c-23.783-17.3-56.217-17.3-80 0'/></g><g transform='matrix(-.555556 0 0 -.555556 200.111 262.393)'><g transform='matrix(1 0 0 1 0 11.5642)'><path d='M200 129c-17.342-13.728-37.723-21.795-58.636-24.198C111.574 101.378 80.703 109.444 56 129' fill='none' stroke='#fff' stroke-width='13.88'/></g><path d='M168 165c-23.783-17.3-56.217-17.3-80 0' fill='none' stroke='#fff' stroke-width='13.88'/></g><g transform='matrix(.75 0 0 .75 32 32)'><path d='M24 72h208v93.881H24z' fill='none' stroke='#fff' stroke-linejoin='miter' stroke-width='9.485'/><circle cx='188' cy='128' r='12' stroke-width='10' transform='matrix(.708333 0 0 .708333 71.8333 12.8333)'/><path d='M24.358 103.5h110' fill='none' stroke='#fff' stroke-linecap='butt' stroke-width='10.282'/></g></g></g></g>\n</svg>\n";
|
var remote_play_default = "<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='4' viewBox='0 0 32 32'>\n <g transform='matrix(.492308 0 0 .581818 -14.7692 -11.6364)'><clipPath id='A'><path d='M30 20h65v55H30z'/></clipPath><g clip-path='url(#A)'><g transform='matrix(.395211 0 0 .334409 11.913 7.01124)'><g transform='matrix(.555556 0 0 .555556 57.8889 -20.2417)' fill='none' stroke='#fff' stroke-width='13.88'><path d='M200 140.564c-42.045-33.285-101.955-33.285-144 0M168 165c-23.783-17.3-56.217-17.3-80 0'/></g><g transform='matrix(-.555556 0 0 -.555556 200.111 262.393)'><g transform='matrix(1 0 0 1 0 11.5642)'><path d='M200 129c-17.342-13.728-37.723-21.795-58.636-24.198C111.574 101.378 80.703 109.444 56 129' fill='none' stroke='#fff' stroke-width='13.88'/></g><path d='M168 165c-23.783-17.3-56.217-17.3-80 0' fill='none' stroke='#fff' stroke-width='13.88'/></g><g transform='matrix(.75 0 0 .75 32 32)'><path d='M24 72h208v93.881H24z' fill='none' stroke='#fff' stroke-linejoin='miter' stroke-width='9.485'/><circle cx='188' cy='128' r='12' stroke-width='10' transform='matrix(.708333 0 0 .708333 71.8333 12.8333)'/><path d='M24.358 103.5h110' fill='none' stroke='#fff' stroke-linecap='butt' stroke-width='10.282'/></g></g></g></g>\n</svg>\n";
|
||||||
|
|
||||||
@ -5012,6 +5022,7 @@ var BxIcon = {
|
|||||||
TRASH: trash_default,
|
TRASH: trash_default,
|
||||||
CURSOR_TEXT: cursor_text_default,
|
CURSOR_TEXT: cursor_text_default,
|
||||||
QUESTION: question_default,
|
QUESTION: question_default,
|
||||||
|
REFRESH: refresh_default,
|
||||||
REMOTE_PLAY: remote_play_default
|
REMOTE_PLAY: remote_play_default
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -5475,7 +5486,7 @@ function injectStreamMenuButtons() {
|
|||||||
$btnClose && $btnClose.click();
|
$btnClose && $btnClose.click();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($elm.className.startsWith("StreamMenu")) {
|
if ($elm.className.startsWith("StreamMenu-module__container")) {
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_MENU_SHOWN);
|
BxEvent.dispatch(window, BxEvent.STREAM_MENU_SHOWN);
|
||||||
const $btnCloseHud = document.querySelector("button[class*=StreamMenu-module__backButton]");
|
const $btnCloseHud = document.querySelector("button[class*=StreamMenu-module__backButton]");
|
||||||
if (!$btnCloseHud) {
|
if (!$btnCloseHud) {
|
||||||
@ -5484,10 +5495,17 @@ function injectStreamMenuButtons() {
|
|||||||
$btnCloseHud && $btnCloseHud.addEventListener("click", (e) => {
|
$btnCloseHud && $btnCloseHud.addEventListener("click", (e) => {
|
||||||
$quickBar.classList.add("bx-gone");
|
$quickBar.classList.add("bx-gone");
|
||||||
});
|
});
|
||||||
const $btnQuit = $elm.querySelector("div[class^=StreamMenu] > div > button:last-child");
|
const $btnRefresh = $btnCloseHud.cloneNode(true);
|
||||||
new MouseHoldEvent($btnQuit, () => {
|
const $svgRefresh = createSvgIcon(BxIcon.REFRESH);
|
||||||
|
$svgRefresh.setAttribute("class", $btnRefresh.firstElementChild.getAttribute("class") || "");
|
||||||
|
$svgRefresh.style.fill = "none";
|
||||||
|
$btnRefresh.classList.add("bx-stream-refresh-button");
|
||||||
|
$btnRefresh.removeChild($btnRefresh.firstElementChild);
|
||||||
|
$btnRefresh.appendChild($svgRefresh);
|
||||||
|
$btnRefresh.addEventListener("click", (e) => {
|
||||||
confirm(t("confirm-reload-stream")) && window.location.reload();
|
confirm(t("confirm-reload-stream")) && window.location.reload();
|
||||||
}, 1000);
|
});
|
||||||
|
$btnCloseHud.insertAdjacentElement("afterend", $btnRefresh);
|
||||||
const $menu = document.querySelector("div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]");
|
const $menu = document.querySelector("div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]");
|
||||||
$menu?.appendChild(await StreamBadges.render());
|
$menu?.appendChild(await StreamBadges.render());
|
||||||
hideQuickBarFunc();
|
hideQuickBarFunc();
|
||||||
@ -5574,44 +5592,6 @@ function showStreamSettings(tabId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MouseHoldEvent {
|
|
||||||
#isHolding = false;
|
|
||||||
#timeout;
|
|
||||||
#$elm;
|
|
||||||
#callback;
|
|
||||||
#duration;
|
|
||||||
#onMouseDown(e) {
|
|
||||||
const _this = this;
|
|
||||||
this.#isHolding = false;
|
|
||||||
this.#timeout && clearTimeout(this.#timeout);
|
|
||||||
this.#timeout = window.setTimeout(() => {
|
|
||||||
_this.#isHolding = true;
|
|
||||||
_this.#callback();
|
|
||||||
}, this.#duration);
|
|
||||||
}
|
|
||||||
#onMouseUp(e) {
|
|
||||||
this.#timeout && clearTimeout(this.#timeout);
|
|
||||||
this.#timeout = null;
|
|
||||||
if (this.#isHolding) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
this.#isHolding = false;
|
|
||||||
}
|
|
||||||
#addEventListeners = () => {
|
|
||||||
this.#$elm.addEventListener("mousedown", this.#onMouseDown.bind(this));
|
|
||||||
this.#$elm.addEventListener("click", this.#onMouseUp.bind(this));
|
|
||||||
this.#$elm.addEventListener("touchstart", this.#onMouseDown.bind(this));
|
|
||||||
this.#$elm.addEventListener("touchend", this.#onMouseUp.bind(this));
|
|
||||||
};
|
|
||||||
constructor($elm, callback, duration = 1000) {
|
|
||||||
this.#$elm = $elm;
|
|
||||||
this.#callback = callback;
|
|
||||||
this.#duration = duration;
|
|
||||||
this.#addEventListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/modules/mkb/mkb-handler.ts
|
// src/modules/mkb/mkb-handler.ts
|
||||||
var LOG_TAG = "MkbHandler";
|
var LOG_TAG = "MkbHandler";
|
||||||
|
|
||||||
@ -6647,15 +6627,13 @@ class TouchController {
|
|||||||
layout: {
|
layout: {
|
||||||
id: "System.Standard",
|
id: "System.Standard",
|
||||||
displayName: "System",
|
displayName: "System",
|
||||||
layoutFile: {
|
layoutFile: layout
|
||||||
content: layout.content
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
static setup() {
|
static setup() {
|
||||||
window.BX_EXPOSED.test_touch_control = (content) => {
|
window.BX_EXPOSED.test_touch_control = (layout) => {
|
||||||
const { touch_layout_manager } = window.BX_EXPOSED;
|
const { touch_layout_manager } = window.BX_EXPOSED;
|
||||||
touch_layout_manager && touch_layout_manager.changeLayoutForScope({
|
touch_layout_manager && touch_layout_manager.changeLayoutForScope({
|
||||||
type: "showLayout",
|
type: "showLayout",
|
||||||
@ -6664,9 +6642,7 @@ class TouchController {
|
|||||||
layout: {
|
layout: {
|
||||||
id: "System.Standard",
|
id: "System.Standard",
|
||||||
displayName: "Custom",
|
displayName: "Custom",
|
||||||
layoutFile: {
|
layoutFile: layout
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -8548,6 +8524,12 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
|
|||||||
background-color: #2d2d2d !important;
|
background-color: #2d2d2d !important;
|
||||||
color: #000 !important;
|
color: #000 !important;
|
||||||
}
|
}
|
||||||
|
.bx-stream-refresh-button {
|
||||||
|
top: calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important;
|
||||||
|
}
|
||||||
|
body[data-media-type=tv] .bx-stream-refresh-button {
|
||||||
|
top: calc(var(--gds-focus-borderSize) + 80px) !important;
|
||||||
|
}
|
||||||
.bx-number-stepper span {
|
.bx-number-stepper span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
@ -9148,324 +9130,6 @@ class MouseCursorHider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/modules/ui/global-settings.ts
|
|
||||||
function setupSettingsUi() {
|
|
||||||
if (document.querySelector(".bx-settings-container")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const PREF_PREFERRED_REGION = getPreferredServerRegion();
|
|
||||||
const PREF_LATEST_VERSION = getPref(PrefKey.LATEST_VERSION);
|
|
||||||
let $reloadBtnWrapper;
|
|
||||||
const $container = CE("div", {
|
|
||||||
class: "bx-settings-container bx-gone"
|
|
||||||
});
|
|
||||||
let $updateAvailable;
|
|
||||||
const $wrapper = CE("div", { class: "bx-settings-wrapper" }, CE("div", { class: "bx-settings-title-wrapper" }, CE("a", {
|
|
||||||
class: "bx-settings-title",
|
|
||||||
href: SCRIPT_HOME,
|
|
||||||
target: "_blank"
|
|
||||||
}, "Better xCloud " + SCRIPT_VERSION), createButton({ icon: BxIcon.QUESTION, label: t("help"), url: "https://better-xcloud.github.io/features/" })));
|
|
||||||
$updateAvailable = CE("a", {
|
|
||||||
class: "bx-settings-update bx-gone",
|
|
||||||
href: "https://github.com/redphx/better-xcloud/releases",
|
|
||||||
target: "_blank"
|
|
||||||
});
|
|
||||||
$wrapper.appendChild($updateAvailable);
|
|
||||||
if (PREF_LATEST_VERSION && PREF_LATEST_VERSION != SCRIPT_VERSION) {
|
|
||||||
$updateAvailable.textContent = `🌟 Version ${PREF_LATEST_VERSION} available`;
|
|
||||||
$updateAvailable.classList.remove("bx-gone");
|
|
||||||
}
|
|
||||||
if (!AppInterface) {
|
|
||||||
const userAgent = UserAgent.getDefault().toLowerCase();
|
|
||||||
if (userAgent.includes("android")) {
|
|
||||||
const $btn = createButton({
|
|
||||||
label: "🔥 " + t("install-android"),
|
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
|
||||||
url: "https://better-xcloud.github.io/android"
|
|
||||||
});
|
|
||||||
$wrapper.appendChild($btn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const onChange = (e) => {
|
|
||||||
if (!$reloadBtnWrapper) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$reloadBtnWrapper.classList.remove("bx-gone");
|
|
||||||
if (e.target.id === "bx_setting_" + PrefKey.BETTER_XCLOUD_LOCALE) {
|
|
||||||
refreshCurrentLocale();
|
|
||||||
const $btn = $reloadBtnWrapper.firstElementChild;
|
|
||||||
$btn.textContent = t("settings-reloading");
|
|
||||||
$btn.click();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for (let groupLabel in SETTINGS_UI) {
|
|
||||||
const $group = CE("span", { class: "bx-settings-group-label" }, groupLabel);
|
|
||||||
if (SETTINGS_UI[groupLabel].note) {
|
|
||||||
const $note = CE("b", {}, SETTINGS_UI[groupLabel].note);
|
|
||||||
$group.appendChild($note);
|
|
||||||
}
|
|
||||||
$wrapper.appendChild($group);
|
|
||||||
if (SETTINGS_UI[groupLabel].unsupported) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const settingItems = SETTINGS_UI[groupLabel].items;
|
|
||||||
for (let settingId of settingItems) {
|
|
||||||
if (!settingId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const setting = Preferences.SETTINGS[settingId];
|
|
||||||
if (!setting) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let settingLabel = setting.label;
|
|
||||||
let settingNote = setting.note || "";
|
|
||||||
if (setting.experimental) {
|
|
||||||
settingLabel = "🧪 " + settingLabel;
|
|
||||||
if (!settingNote) {
|
|
||||||
settingNote = t("experimental");
|
|
||||||
} else {
|
|
||||||
settingNote = `${t("experimental")}: ${settingNote}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let $control;
|
|
||||||
let $inpCustomUserAgent;
|
|
||||||
let labelAttrs = {};
|
|
||||||
if (settingId === PrefKey.USER_AGENT_PROFILE) {
|
|
||||||
let defaultUserAgent = window.navigator.orgUserAgent || window.navigator.userAgent;
|
|
||||||
$inpCustomUserAgent = CE("input", {
|
|
||||||
type: "text",
|
|
||||||
placeholder: defaultUserAgent,
|
|
||||||
class: "bx-settings-custom-user-agent"
|
|
||||||
});
|
|
||||||
$inpCustomUserAgent.addEventListener("change", (e) => {
|
|
||||||
setPref(PrefKey.USER_AGENT_CUSTOM, e.target.value.trim());
|
|
||||||
onChange(e);
|
|
||||||
});
|
|
||||||
$control = toPrefElement(PrefKey.USER_AGENT_PROFILE, (e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
let isCustom = value === UserAgentProfile.CUSTOM;
|
|
||||||
let userAgent = UserAgent.get(value);
|
|
||||||
$inpCustomUserAgent.value = userAgent;
|
|
||||||
$inpCustomUserAgent.readOnly = !isCustom;
|
|
||||||
$inpCustomUserAgent.disabled = !isCustom;
|
|
||||||
onChange(e);
|
|
||||||
});
|
|
||||||
} else if (settingId === PrefKey.SERVER_REGION) {
|
|
||||||
let selectedValue;
|
|
||||||
$control = CE("select", { id: `bx_setting_${settingId}` });
|
|
||||||
$control.name = $control.id;
|
|
||||||
$control.addEventListener("change", (e) => {
|
|
||||||
setPref(settingId, e.target.value);
|
|
||||||
onChange(e);
|
|
||||||
});
|
|
||||||
selectedValue = PREF_PREFERRED_REGION;
|
|
||||||
setting.options = {};
|
|
||||||
for (let regionName in STATES.serverRegions) {
|
|
||||||
const region4 = STATES.serverRegions[regionName];
|
|
||||||
let value = regionName;
|
|
||||||
let label = `${region4.shortName} - ${regionName}`;
|
|
||||||
if (region4.isDefault) {
|
|
||||||
label += ` (${t("default")})`;
|
|
||||||
value = "default";
|
|
||||||
if (selectedValue === regionName) {
|
|
||||||
selectedValue = "default";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setting.options[value] = label;
|
|
||||||
}
|
|
||||||
for (let value in setting.options) {
|
|
||||||
const label = setting.options[value];
|
|
||||||
const $option = CE("option", { value }, label);
|
|
||||||
$control.appendChild($option);
|
|
||||||
}
|
|
||||||
$control.value = selectedValue;
|
|
||||||
} else {
|
|
||||||
if (settingId === PrefKey.BETTER_XCLOUD_LOCALE) {
|
|
||||||
$control = toPrefElement(settingId, (e) => {
|
|
||||||
localStorage.setItem("better_xcloud_locale", e.target.value);
|
|
||||||
onChange(e);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$control = toPrefElement(settingId, onChange);
|
|
||||||
}
|
|
||||||
labelAttrs = { for: $control.id, tabindex: 0 };
|
|
||||||
}
|
|
||||||
if (setting.unsupported) {
|
|
||||||
$control.disabled = true;
|
|
||||||
}
|
|
||||||
const $label = CE("label", labelAttrs, settingLabel);
|
|
||||||
if (settingNote) {
|
|
||||||
$label.appendChild(CE("b", {}, settingNote));
|
|
||||||
}
|
|
||||||
const $elm = CE("div", { class: "bx-settings-row" }, $label, $control);
|
|
||||||
$wrapper.appendChild($elm);
|
|
||||||
if (settingId === PrefKey.USER_AGENT_PROFILE) {
|
|
||||||
$wrapper.appendChild($inpCustomUserAgent);
|
|
||||||
$control.dispatchEvent(new Event("change"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const $reloadBtn = createButton({
|
|
||||||
label: t("settings-reload"),
|
|
||||||
style: ButtonStyle.DANGER | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_WIDTH,
|
|
||||||
onClick: (e) => {
|
|
||||||
window.location.reload();
|
|
||||||
$reloadBtn.disabled = true;
|
|
||||||
$reloadBtn.textContent = t("settings-reloading");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$reloadBtn.setAttribute("tabindex", "0");
|
|
||||||
$reloadBtnWrapper = CE("div", { class: "bx-settings-reload-button-wrapper bx-gone" }, $reloadBtn);
|
|
||||||
$wrapper.appendChild($reloadBtnWrapper);
|
|
||||||
const $donationLink = CE("a", { class: "bx-donation-link", href: "https://ko-fi.com/redphx", target: "_blank" }, `❤️ ${t("support-better-xcloud")}`);
|
|
||||||
$wrapper.appendChild($donationLink);
|
|
||||||
try {
|
|
||||||
const appVersion = document.querySelector("meta[name=gamepass-app-version]").content;
|
|
||||||
const appDate = new Date(document.querySelector("meta[name=gamepass-app-date]").content).toISOString().substring(0, 10);
|
|
||||||
$wrapper.appendChild(CE("div", { class: "bx-settings-app-version" }, `xCloud website version ${appVersion} (${appDate})`));
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
$container.appendChild($wrapper);
|
|
||||||
const $pageContent = document.getElementById("PageContent");
|
|
||||||
$pageContent?.parentNode?.insertBefore($container, $pageContent);
|
|
||||||
}
|
|
||||||
var SETTINGS_UI = {
|
|
||||||
"Better xCloud": {
|
|
||||||
items: [
|
|
||||||
PrefKey.BETTER_XCLOUD_LOCALE,
|
|
||||||
PrefKey.REMOTE_PLAY_ENABLED
|
|
||||||
]
|
|
||||||
},
|
|
||||||
[t("server")]: {
|
|
||||||
items: [
|
|
||||||
PrefKey.SERVER_REGION,
|
|
||||||
PrefKey.STREAM_PREFERRED_LOCALE,
|
|
||||||
PrefKey.PREFER_IPV6_SERVER
|
|
||||||
]
|
|
||||||
},
|
|
||||||
[t("stream")]: {
|
|
||||||
items: [
|
|
||||||
PrefKey.STREAM_TARGET_RESOLUTION,
|
|
||||||
PrefKey.STREAM_CODEC_PROFILE,
|
|
||||||
PrefKey.GAME_FORTNITE_FORCE_CONSOLE,
|
|
||||||
PrefKey.AUDIO_MIC_ON_PLAYING,
|
|
||||||
PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG,
|
|
||||||
PrefKey.SCREENSHOT_BUTTON_POSITION,
|
|
||||||
PrefKey.SCREENSHOT_APPLY_FILTERS,
|
|
||||||
PrefKey.AUDIO_ENABLE_VOLUME_CONTROL,
|
|
||||||
PrefKey.STREAM_COMBINE_SOURCES
|
|
||||||
]
|
|
||||||
},
|
|
||||||
[t("local-co-op")]: {
|
|
||||||
items: [
|
|
||||||
PrefKey.LOCAL_CO_OP_ENABLED
|
|
||||||
]
|
|
||||||
},
|
|
||||||
[t("mouse-and-keyboard")]: {
|
|
||||||
items: [
|
|
||||||
PrefKey.MKB_ENABLED,
|
|
||||||
PrefKey.MKB_HIDE_IDLE_CURSOR
|
|
||||||
]
|
|
||||||
},
|
|
||||||
[t("touch-controller")]: {
|
|
||||||
note: !STATES.hasTouchSupport ? "⚠️ " + t("device-unsupported-touch") : null,
|
|
||||||
unsupported: !STATES.hasTouchSupport,
|
|
||||||
items: [
|
|
||||||
PrefKey.STREAM_TOUCH_CONTROLLER,
|
|
||||||
PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF,
|
|
||||||
PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD,
|
|
||||||
PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM
|
|
||||||
]
|
|
||||||
},
|
|
||||||
[t("loading-screen")]: {
|
|
||||||
items: [
|
|
||||||
PrefKey.UI_LOADING_SCREEN_GAME_ART,
|
|
||||||
PrefKey.UI_LOADING_SCREEN_WAIT_TIME,
|
|
||||||
PrefKey.UI_LOADING_SCREEN_ROCKET
|
|
||||||
]
|
|
||||||
},
|
|
||||||
[t("ui")]: {
|
|
||||||
items: [
|
|
||||||
PrefKey.UI_LAYOUT,
|
|
||||||
PrefKey.STREAM_SIMPLIFY_MENU,
|
|
||||||
PrefKey.SKIP_SPLASH_VIDEO,
|
|
||||||
!AppInterface && PrefKey.UI_SCROLLBAR_HIDE,
|
|
||||||
PrefKey.HIDE_DOTS_ICON,
|
|
||||||
PrefKey.REDUCE_ANIMATIONS
|
|
||||||
]
|
|
||||||
},
|
|
||||||
[t("other")]: {
|
|
||||||
items: [
|
|
||||||
PrefKey.BLOCK_SOCIAL_FEATURES,
|
|
||||||
PrefKey.BLOCK_TRACKING
|
|
||||||
]
|
|
||||||
},
|
|
||||||
[t("advanced")]: {
|
|
||||||
items: [
|
|
||||||
PrefKey.USER_AGENT_PROFILE
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// src/modules/ui/header.ts
|
|
||||||
var injectSettingsButton = function($parent) {
|
|
||||||
if (!$parent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const PREF_PREFERRED_REGION = getPreferredServerRegion(true);
|
|
||||||
const PREF_LATEST_VERSION = getPref(PrefKey.LATEST_VERSION);
|
|
||||||
const $headerFragment = document.createDocumentFragment();
|
|
||||||
if (getPref(PrefKey.REMOTE_PLAY_ENABLED)) {
|
|
||||||
const $remotePlayBtn = createButton({
|
|
||||||
classes: ["bx-header-remote-play-button"],
|
|
||||||
icon: BxIcon.REMOTE_PLAY,
|
|
||||||
title: t("remote-play"),
|
|
||||||
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE,
|
|
||||||
onClick: (e) => {
|
|
||||||
RemotePlay.togglePopup();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$headerFragment.appendChild($remotePlayBtn);
|
|
||||||
}
|
|
||||||
const $settingsBtn = createButton({
|
|
||||||
classes: ["bx-header-settings-button"],
|
|
||||||
label: PREF_PREFERRED_REGION,
|
|
||||||
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_HEIGHT,
|
|
||||||
onClick: (e) => {
|
|
||||||
setupSettingsUi();
|
|
||||||
const $settings = document.querySelector(".bx-settings-container");
|
|
||||||
$settings.classList.toggle("bx-gone");
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
document.activeElement && document.activeElement.blur();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) {
|
|
||||||
$settingsBtn.setAttribute("data-update-available", "true");
|
|
||||||
}
|
|
||||||
$headerFragment.appendChild($settingsBtn);
|
|
||||||
$parent.appendChild($headerFragment);
|
|
||||||
};
|
|
||||||
function checkHeader() {
|
|
||||||
const $button = document.querySelector(".bx-header-settings-button");
|
|
||||||
if (!$button) {
|
|
||||||
const $rightHeader = document.querySelector("#PageContent div[class*=EdgewaterHeader-module__rightSectionSpacing]");
|
|
||||||
injectSettingsButton($rightHeader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function watchHeader() {
|
|
||||||
const $header = document.querySelector("#PageContent header");
|
|
||||||
if (!$header) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let timeout;
|
|
||||||
const observer = new MutationObserver((mutationList) => {
|
|
||||||
timeout && clearTimeout(timeout);
|
|
||||||
timeout = window.setTimeout(checkHeader, 2000);
|
|
||||||
});
|
|
||||||
observer.observe($header, { subtree: true, childList: true });
|
|
||||||
checkHeader();
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/utils/utils.ts
|
// src/utils/utils.ts
|
||||||
function checkForUpdate() {
|
function checkForUpdate() {
|
||||||
const CHECK_INTERVAL_SECONDS = 7200;
|
const CHECK_INTERVAL_SECONDS = 7200;
|
||||||
@ -9548,12 +9212,13 @@ var PATCHES = {
|
|||||||
}
|
}
|
||||||
return str2.replace(text, text + "return;");
|
return str2.replace(text, text + "return;");
|
||||||
},
|
},
|
||||||
tvLayout(str2) {
|
websiteLayout(str2) {
|
||||||
const text = '?"tv":"default"';
|
const text = '?"tv":"default"';
|
||||||
if (!str2.includes(text)) {
|
if (!str2.includes(text)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return str2.replace(text, '?"tv":"tv"');
|
const layout = getPref(PrefKey.UI_LAYOUT) === "tv" ? "tv" : "default";
|
||||||
|
return str2.replace(text, `?"${layout}":"${layout}"`);
|
||||||
},
|
},
|
||||||
remotePlayDirectConnectUrl(str2) {
|
remotePlayDirectConnectUrl(str2) {
|
||||||
const index = str2.indexOf("/direct-connect");
|
const index = str2.indexOf("/direct-connect");
|
||||||
@ -9856,7 +9521,7 @@ var PATCH_ORDERS = [
|
|||||||
"disableStreamGate",
|
"disableStreamGate",
|
||||||
"overrideSettings",
|
"overrideSettings",
|
||||||
"broadcastPollingMode",
|
"broadcastPollingMode",
|
||||||
getPref(PrefKey.UI_LAYOUT) === "tv" && "tvLayout",
|
getPref(PrefKey.UI_LAYOUT) !== "default" && "websiteLayout",
|
||||||
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && "supportLocalCoOp",
|
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && "supportLocalCoOp",
|
||||||
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && "forceFortniteConsole",
|
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && "forceFortniteConsole",
|
||||||
...getPref(PrefKey.BLOCK_TRACKING) ? [
|
...getPref(PrefKey.BLOCK_TRACKING) ? [
|
||||||
@ -9908,6 +9573,7 @@ class Patcher {
|
|||||||
if (!valid) {
|
if (!valid) {
|
||||||
return nativeBind.apply(this, arguments);
|
return nativeBind.apply(this, arguments);
|
||||||
}
|
}
|
||||||
|
PatcherCache.init();
|
||||||
if (typeof arguments[1] === "function") {
|
if (typeof arguments[1] === "function") {
|
||||||
BxLogger.info(LOG_TAG4, "Restored Function.prototype.bind()");
|
BxLogger.info(LOG_TAG4, "Restored Function.prototype.bind()");
|
||||||
Function.prototype.bind = nativeBind;
|
Function.prototype.bind = nativeBind;
|
||||||
@ -9920,33 +9586,33 @@ class Patcher {
|
|||||||
return nativeBind.apply(newFunc, arguments);
|
return nativeBind.apply(newFunc, arguments);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
static length() {
|
|
||||||
return PATCH_ORDERS.length;
|
|
||||||
}
|
|
||||||
static patch(item) {
|
static patch(item) {
|
||||||
let patchesToCheck;
|
let patchesToCheck;
|
||||||
let appliedPatches;
|
let appliedPatches;
|
||||||
const caches = {};
|
const patchesMap = {};
|
||||||
for (let id in item[1]) {
|
for (let id in item[1]) {
|
||||||
appliedPatches = [];
|
appliedPatches = [];
|
||||||
const cachedPatches = PatcherCache.getPatches(id);
|
const cachedPatches = PatcherCache.getPatches(id);
|
||||||
if (cachedPatches) {
|
if (cachedPatches) {
|
||||||
patchesToCheck = cachedPatches;
|
patchesToCheck = cachedPatches.slice(0);
|
||||||
patchesToCheck.push(...PATCH_ORDERS);
|
patchesToCheck.push(...PATCH_ORDERS);
|
||||||
} else {
|
} else {
|
||||||
patchesToCheck = PATCH_ORDERS;
|
patchesToCheck = PATCH_ORDERS.slice(0);
|
||||||
}
|
}
|
||||||
if (!patchesToCheck.length) {
|
if (!patchesToCheck.length) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const func = item[1][id];
|
const func = item[1][id];
|
||||||
let str = func.toString();
|
let str = func.toString();
|
||||||
for (let groupIndex = 0;groupIndex < patchesToCheck.length; groupIndex++) {
|
let modified = false;
|
||||||
const patchName = patchesToCheck[groupIndex];
|
for (let patchIndex = 0;patchIndex < patchesToCheck.length; patchIndex++) {
|
||||||
let modified = false;
|
const patchName = patchesToCheck[patchIndex];
|
||||||
if (appliedPatches.indexOf(patchName) > -1) {
|
if (appliedPatches.indexOf(patchName) > -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!PATCHES[patchName]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const patchedStr = PATCHES[patchName].call(null, str);
|
const patchedStr = PATCHES[patchName].call(null, str);
|
||||||
if (!patchedStr) {
|
if (!patchedStr) {
|
||||||
continue;
|
continue;
|
||||||
@ -9955,19 +9621,19 @@ class Patcher {
|
|||||||
str = patchedStr;
|
str = patchedStr;
|
||||||
BxLogger.info(LOG_TAG4, `Applied "${patchName}" patch`);
|
BxLogger.info(LOG_TAG4, `Applied "${patchName}" patch`);
|
||||||
appliedPatches.push(patchName);
|
appliedPatches.push(patchName);
|
||||||
patchesToCheck.splice(groupIndex, 1);
|
patchesToCheck.splice(patchIndex, 1);
|
||||||
groupIndex--;
|
patchIndex--;
|
||||||
PATCH_ORDERS = PATCH_ORDERS.filter((item2) => item2 != patchName);
|
PATCH_ORDERS = PATCH_ORDERS.filter((item2) => item2 != patchName);
|
||||||
if (modified) {
|
}
|
||||||
item[1][id] = eval(str);
|
if (modified) {
|
||||||
}
|
item[1][id] = eval(str);
|
||||||
}
|
}
|
||||||
if (appliedPatches.length) {
|
if (appliedPatches.length) {
|
||||||
caches[id] = appliedPatches;
|
patchesMap[id] = appliedPatches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Object.keys(caches).length) {
|
if (Object.keys(patchesMap).length) {
|
||||||
PatcherCache.saveToCache(caches);
|
PatcherCache.saveToCache(patchesMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static init() {
|
static init() {
|
||||||
@ -9979,6 +9645,7 @@ class PatcherCache {
|
|||||||
static #KEY_CACHE = "better_xcloud_patches_cache";
|
static #KEY_CACHE = "better_xcloud_patches_cache";
|
||||||
static #KEY_SIGNATURE = "better_xcloud_patches_cache_signature";
|
static #KEY_SIGNATURE = "better_xcloud_patches_cache_signature";
|
||||||
static #CACHE;
|
static #CACHE;
|
||||||
|
static #isInitialized = false;
|
||||||
static #getSignature() {
|
static #getSignature() {
|
||||||
const scriptVersion = SCRIPT_VERSION;
|
const scriptVersion = SCRIPT_VERSION;
|
||||||
const webVersion = document.querySelector("meta[name=gamepass-app-version]")?.content;
|
const webVersion = document.querySelector("meta[name=gamepass-app-version]")?.content;
|
||||||
@ -9986,13 +9653,17 @@ class PatcherCache {
|
|||||||
const sig = hashCode(scriptVersion + webVersion + patches);
|
const sig = hashCode(scriptVersion + webVersion + patches);
|
||||||
return sig;
|
return sig;
|
||||||
}
|
}
|
||||||
|
static clear() {
|
||||||
|
window.localStorage.removeItem(PatcherCache.#KEY_CACHE);
|
||||||
|
PatcherCache.#CACHE = {};
|
||||||
|
}
|
||||||
static checkSignature() {
|
static checkSignature() {
|
||||||
const storedSig = window.localStorage.getItem(PatcherCache.#KEY_SIGNATURE) || 0;
|
const storedSig = window.localStorage.getItem(PatcherCache.#KEY_SIGNATURE) || 0;
|
||||||
const currentSig = PatcherCache.#getSignature();
|
const currentSig = PatcherCache.#getSignature();
|
||||||
if (currentSig !== parseInt(storedSig)) {
|
if (currentSig !== parseInt(storedSig)) {
|
||||||
BxLogger.warning(LOG_TAG4, "Signature changed");
|
BxLogger.warning(LOG_TAG4, "Signature changed");
|
||||||
window.localStorage.setItem(PatcherCache.#KEY_CACHE, "{}");
|
|
||||||
window.localStorage.setItem(PatcherCache.#KEY_SIGNATURE, currentSig.toString());
|
window.localStorage.setItem(PatcherCache.#KEY_SIGNATURE, currentSig.toString());
|
||||||
|
PatcherCache.clear();
|
||||||
} else {
|
} else {
|
||||||
BxLogger.info(LOG_TAG4, "Signature unchanged");
|
BxLogger.info(LOG_TAG4, "Signature unchanged");
|
||||||
}
|
}
|
||||||
@ -10018,9 +9689,9 @@ class PatcherCache {
|
|||||||
if (!data) {
|
if (!data) {
|
||||||
PatcherCache.#CACHE[id2] = patchNames;
|
PatcherCache.#CACHE[id2] = patchNames;
|
||||||
} else {
|
} else {
|
||||||
for (const patchName2 of patchNames) {
|
for (const patchName of patchNames) {
|
||||||
if (!data.includes(patchName2)) {
|
if (!data.includes(patchName)) {
|
||||||
data.push(patchName2);
|
data.push(patchName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -10028,6 +9699,11 @@ class PatcherCache {
|
|||||||
window.localStorage.setItem(PatcherCache.#KEY_CACHE, JSON.stringify(PatcherCache.#CACHE));
|
window.localStorage.setItem(PatcherCache.#KEY_CACHE, JSON.stringify(PatcherCache.#CACHE));
|
||||||
}
|
}
|
||||||
static init() {
|
static init() {
|
||||||
|
if (PatcherCache.#isInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PatcherCache.#isInitialized = true;
|
||||||
|
PatcherCache.checkSignature();
|
||||||
PatcherCache.#CACHE = JSON.parse(window.localStorage.getItem(PatcherCache.#KEY_CACHE) || "{}");
|
PatcherCache.#CACHE = JSON.parse(window.localStorage.getItem(PatcherCache.#KEY_CACHE) || "{}");
|
||||||
BxLogger.info(LOG_TAG4, PatcherCache.#CACHE);
|
BxLogger.info(LOG_TAG4, PatcherCache.#CACHE);
|
||||||
if (window.location.pathname.includes("/play/")) {
|
if (window.location.pathname.includes("/play/")) {
|
||||||
@ -10041,12 +9717,325 @@ class PatcherCache {
|
|||||||
BxLogger.info(LOG_TAG4, PLAYING_PATCH_ORDERS.slice(0));
|
BxLogger.info(LOG_TAG4, PLAYING_PATCH_ORDERS.slice(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.addEventListener("readystatechange", (e) => {
|
|
||||||
if (document.readyState === "interactive") {
|
// src/modules/ui/global-settings.ts
|
||||||
PatcherCache.checkSignature();
|
function setupSettingsUi() {
|
||||||
|
if (document.querySelector(".bx-settings-container")) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
const PREF_PREFERRED_REGION = getPreferredServerRegion();
|
||||||
PatcherCache.init();
|
const PREF_LATEST_VERSION = getPref(PrefKey.LATEST_VERSION);
|
||||||
|
let $reloadBtnWrapper;
|
||||||
|
const $container = CE("div", {
|
||||||
|
class: "bx-settings-container bx-gone"
|
||||||
|
});
|
||||||
|
let $updateAvailable;
|
||||||
|
const $wrapper = CE("div", { class: "bx-settings-wrapper" }, CE("div", { class: "bx-settings-title-wrapper" }, CE("a", {
|
||||||
|
class: "bx-settings-title",
|
||||||
|
href: SCRIPT_HOME,
|
||||||
|
target: "_blank"
|
||||||
|
}, "Better xCloud " + SCRIPT_VERSION), createButton({ icon: BxIcon.QUESTION, label: t("help"), url: "https://better-xcloud.github.io/features/" })));
|
||||||
|
$updateAvailable = CE("a", {
|
||||||
|
class: "bx-settings-update bx-gone",
|
||||||
|
href: "https://github.com/redphx/better-xcloud/releases",
|
||||||
|
target: "_blank"
|
||||||
|
});
|
||||||
|
$wrapper.appendChild($updateAvailable);
|
||||||
|
if (PREF_LATEST_VERSION && PREF_LATEST_VERSION != SCRIPT_VERSION) {
|
||||||
|
$updateAvailable.textContent = `🌟 Version ${PREF_LATEST_VERSION} available`;
|
||||||
|
$updateAvailable.classList.remove("bx-gone");
|
||||||
|
}
|
||||||
|
if (!AppInterface) {
|
||||||
|
const userAgent = UserAgent.getDefault().toLowerCase();
|
||||||
|
if (userAgent.includes("android")) {
|
||||||
|
const $btn = createButton({
|
||||||
|
label: "🔥 " + t("install-android"),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
|
url: "https://better-xcloud.github.io/android"
|
||||||
|
});
|
||||||
|
$wrapper.appendChild($btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onChange = (e) => {
|
||||||
|
if (!$reloadBtnWrapper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$reloadBtnWrapper.classList.remove("bx-gone");
|
||||||
|
PatcherCache.clear();
|
||||||
|
if (e.target.id === "bx_setting_" + PrefKey.BETTER_XCLOUD_LOCALE) {
|
||||||
|
refreshCurrentLocale();
|
||||||
|
const $btn = $reloadBtnWrapper.firstElementChild;
|
||||||
|
$btn.textContent = t("settings-reloading");
|
||||||
|
$btn.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (let groupLabel in SETTINGS_UI) {
|
||||||
|
const $group = CE("span", { class: "bx-settings-group-label" }, groupLabel);
|
||||||
|
if (SETTINGS_UI[groupLabel].note) {
|
||||||
|
const $note = CE("b", {}, SETTINGS_UI[groupLabel].note);
|
||||||
|
$group.appendChild($note);
|
||||||
|
}
|
||||||
|
$wrapper.appendChild($group);
|
||||||
|
if (SETTINGS_UI[groupLabel].unsupported) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const settingItems = SETTINGS_UI[groupLabel].items;
|
||||||
|
for (let settingId of settingItems) {
|
||||||
|
if (!settingId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const setting = Preferences.SETTINGS[settingId];
|
||||||
|
if (!setting) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let settingLabel = setting.label;
|
||||||
|
let settingNote = setting.note || "";
|
||||||
|
if (setting.experimental) {
|
||||||
|
settingLabel = "🧪 " + settingLabel;
|
||||||
|
if (!settingNote) {
|
||||||
|
settingNote = t("experimental");
|
||||||
|
} else {
|
||||||
|
settingNote = `${t("experimental")}: ${settingNote}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let $control;
|
||||||
|
let $inpCustomUserAgent;
|
||||||
|
let labelAttrs = {};
|
||||||
|
if (settingId === PrefKey.USER_AGENT_PROFILE) {
|
||||||
|
let defaultUserAgent = window.navigator.orgUserAgent || window.navigator.userAgent;
|
||||||
|
$inpCustomUserAgent = CE("input", {
|
||||||
|
type: "text",
|
||||||
|
placeholder: defaultUserAgent,
|
||||||
|
class: "bx-settings-custom-user-agent"
|
||||||
|
});
|
||||||
|
$inpCustomUserAgent.addEventListener("change", (e) => {
|
||||||
|
setPref(PrefKey.USER_AGENT_CUSTOM, e.target.value.trim());
|
||||||
|
onChange(e);
|
||||||
|
});
|
||||||
|
$control = toPrefElement(PrefKey.USER_AGENT_PROFILE, (e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
let isCustom = value === UserAgentProfile.CUSTOM;
|
||||||
|
let userAgent = UserAgent.get(value);
|
||||||
|
$inpCustomUserAgent.value = userAgent;
|
||||||
|
$inpCustomUserAgent.readOnly = !isCustom;
|
||||||
|
$inpCustomUserAgent.disabled = !isCustom;
|
||||||
|
onChange(e);
|
||||||
|
});
|
||||||
|
} else if (settingId === PrefKey.SERVER_REGION) {
|
||||||
|
let selectedValue;
|
||||||
|
$control = CE("select", { id: `bx_setting_${settingId}` });
|
||||||
|
$control.name = $control.id;
|
||||||
|
$control.addEventListener("change", (e) => {
|
||||||
|
setPref(settingId, e.target.value);
|
||||||
|
onChange(e);
|
||||||
|
});
|
||||||
|
selectedValue = PREF_PREFERRED_REGION;
|
||||||
|
setting.options = {};
|
||||||
|
for (let regionName in STATES.serverRegions) {
|
||||||
|
const region4 = STATES.serverRegions[regionName];
|
||||||
|
let value = regionName;
|
||||||
|
let label = `${region4.shortName} - ${regionName}`;
|
||||||
|
if (region4.isDefault) {
|
||||||
|
label += ` (${t("default")})`;
|
||||||
|
value = "default";
|
||||||
|
if (selectedValue === regionName) {
|
||||||
|
selectedValue = "default";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setting.options[value] = label;
|
||||||
|
}
|
||||||
|
for (let value in setting.options) {
|
||||||
|
const label = setting.options[value];
|
||||||
|
const $option = CE("option", { value }, label);
|
||||||
|
$control.appendChild($option);
|
||||||
|
}
|
||||||
|
$control.value = selectedValue;
|
||||||
|
} else {
|
||||||
|
if (settingId === PrefKey.BETTER_XCLOUD_LOCALE) {
|
||||||
|
$control = toPrefElement(settingId, (e) => {
|
||||||
|
localStorage.setItem("better_xcloud_locale", e.target.value);
|
||||||
|
onChange(e);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$control = toPrefElement(settingId, onChange);
|
||||||
|
}
|
||||||
|
labelAttrs = { for: $control.id, tabindex: 0 };
|
||||||
|
}
|
||||||
|
if (setting.unsupported) {
|
||||||
|
$control.disabled = true;
|
||||||
|
}
|
||||||
|
const $label = CE("label", labelAttrs, settingLabel);
|
||||||
|
if (settingNote) {
|
||||||
|
$label.appendChild(CE("b", {}, settingNote));
|
||||||
|
}
|
||||||
|
const $elm = CE("div", { class: "bx-settings-row" }, $label, $control);
|
||||||
|
$wrapper.appendChild($elm);
|
||||||
|
if (settingId === PrefKey.USER_AGENT_PROFILE) {
|
||||||
|
$wrapper.appendChild($inpCustomUserAgent);
|
||||||
|
$control.dispatchEvent(new Event("change"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const $reloadBtn = createButton({
|
||||||
|
label: t("settings-reload"),
|
||||||
|
style: ButtonStyle.DANGER | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_WIDTH,
|
||||||
|
onClick: (e) => {
|
||||||
|
window.location.reload();
|
||||||
|
$reloadBtn.disabled = true;
|
||||||
|
$reloadBtn.textContent = t("settings-reloading");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$reloadBtn.setAttribute("tabindex", "0");
|
||||||
|
$reloadBtnWrapper = CE("div", { class: "bx-settings-reload-button-wrapper bx-gone" }, $reloadBtn);
|
||||||
|
$wrapper.appendChild($reloadBtnWrapper);
|
||||||
|
const $donationLink = CE("a", { class: "bx-donation-link", href: "https://ko-fi.com/redphx", target: "_blank" }, `❤️ ${t("support-better-xcloud")}`);
|
||||||
|
$wrapper.appendChild($donationLink);
|
||||||
|
try {
|
||||||
|
const appVersion = document.querySelector("meta[name=gamepass-app-version]").content;
|
||||||
|
const appDate = new Date(document.querySelector("meta[name=gamepass-app-date]").content).toISOString().substring(0, 10);
|
||||||
|
$wrapper.appendChild(CE("div", { class: "bx-settings-app-version" }, `xCloud website version ${appVersion} (${appDate})`));
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
$container.appendChild($wrapper);
|
||||||
|
const $pageContent = document.getElementById("PageContent");
|
||||||
|
$pageContent?.parentNode?.insertBefore($container, $pageContent);
|
||||||
|
}
|
||||||
|
var SETTINGS_UI = {
|
||||||
|
"Better xCloud": {
|
||||||
|
items: [
|
||||||
|
PrefKey.BETTER_XCLOUD_LOCALE,
|
||||||
|
PrefKey.REMOTE_PLAY_ENABLED
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[t("server")]: {
|
||||||
|
items: [
|
||||||
|
PrefKey.SERVER_REGION,
|
||||||
|
PrefKey.STREAM_PREFERRED_LOCALE,
|
||||||
|
PrefKey.PREFER_IPV6_SERVER
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[t("stream")]: {
|
||||||
|
items: [
|
||||||
|
PrefKey.STREAM_TARGET_RESOLUTION,
|
||||||
|
PrefKey.STREAM_CODEC_PROFILE,
|
||||||
|
PrefKey.GAME_FORTNITE_FORCE_CONSOLE,
|
||||||
|
PrefKey.AUDIO_MIC_ON_PLAYING,
|
||||||
|
PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG,
|
||||||
|
PrefKey.SCREENSHOT_BUTTON_POSITION,
|
||||||
|
PrefKey.SCREENSHOT_APPLY_FILTERS,
|
||||||
|
PrefKey.AUDIO_ENABLE_VOLUME_CONTROL,
|
||||||
|
PrefKey.STREAM_COMBINE_SOURCES
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[t("local-co-op")]: {
|
||||||
|
items: [
|
||||||
|
PrefKey.LOCAL_CO_OP_ENABLED
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[t("mouse-and-keyboard")]: {
|
||||||
|
items: [
|
||||||
|
PrefKey.MKB_ENABLED,
|
||||||
|
PrefKey.MKB_HIDE_IDLE_CURSOR
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[t("touch-controller")]: {
|
||||||
|
note: !STATES.hasTouchSupport ? "⚠️ " + t("device-unsupported-touch") : null,
|
||||||
|
unsupported: !STATES.hasTouchSupport,
|
||||||
|
items: [
|
||||||
|
PrefKey.STREAM_TOUCH_CONTROLLER,
|
||||||
|
PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF,
|
||||||
|
PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD,
|
||||||
|
PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[t("loading-screen")]: {
|
||||||
|
items: [
|
||||||
|
PrefKey.UI_LOADING_SCREEN_GAME_ART,
|
||||||
|
PrefKey.UI_LOADING_SCREEN_WAIT_TIME,
|
||||||
|
PrefKey.UI_LOADING_SCREEN_ROCKET
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[t("ui")]: {
|
||||||
|
items: [
|
||||||
|
PrefKey.UI_LAYOUT,
|
||||||
|
PrefKey.STREAM_SIMPLIFY_MENU,
|
||||||
|
PrefKey.SKIP_SPLASH_VIDEO,
|
||||||
|
!AppInterface && PrefKey.UI_SCROLLBAR_HIDE,
|
||||||
|
PrefKey.HIDE_DOTS_ICON,
|
||||||
|
PrefKey.REDUCE_ANIMATIONS
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[t("other")]: {
|
||||||
|
items: [
|
||||||
|
PrefKey.BLOCK_SOCIAL_FEATURES,
|
||||||
|
PrefKey.BLOCK_TRACKING
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[t("advanced")]: {
|
||||||
|
items: [
|
||||||
|
PrefKey.USER_AGENT_PROFILE
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/modules/ui/header.ts
|
||||||
|
var injectSettingsButton = function($parent) {
|
||||||
|
if (!$parent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const PREF_PREFERRED_REGION = getPreferredServerRegion(true);
|
||||||
|
const PREF_LATEST_VERSION = getPref(PrefKey.LATEST_VERSION);
|
||||||
|
const $headerFragment = document.createDocumentFragment();
|
||||||
|
if (getPref(PrefKey.REMOTE_PLAY_ENABLED)) {
|
||||||
|
const $remotePlayBtn = createButton({
|
||||||
|
classes: ["bx-header-remote-play-button"],
|
||||||
|
icon: BxIcon.REMOTE_PLAY,
|
||||||
|
title: t("remote-play"),
|
||||||
|
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE,
|
||||||
|
onClick: (e) => {
|
||||||
|
RemotePlay.togglePopup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$headerFragment.appendChild($remotePlayBtn);
|
||||||
|
}
|
||||||
|
const $settingsBtn = createButton({
|
||||||
|
classes: ["bx-header-settings-button"],
|
||||||
|
label: PREF_PREFERRED_REGION,
|
||||||
|
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_HEIGHT,
|
||||||
|
onClick: (e) => {
|
||||||
|
setupSettingsUi();
|
||||||
|
const $settings = document.querySelector(".bx-settings-container");
|
||||||
|
$settings.classList.toggle("bx-gone");
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
document.activeElement && document.activeElement.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) {
|
||||||
|
$settingsBtn.setAttribute("data-update-available", "true");
|
||||||
|
}
|
||||||
|
$headerFragment.appendChild($settingsBtn);
|
||||||
|
$parent.appendChild($headerFragment);
|
||||||
|
};
|
||||||
|
function checkHeader() {
|
||||||
|
const $button = document.querySelector(".bx-header-settings-button");
|
||||||
|
if (!$button) {
|
||||||
|
const $rightHeader = document.querySelector("#PageContent div[class*=EdgewaterHeader-module__rightSectionSpacing]");
|
||||||
|
injectSettingsButton($rightHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function watchHeader() {
|
||||||
|
const $header = document.querySelector("#PageContent header");
|
||||||
|
if (!$header) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let timeout;
|
||||||
|
const observer = new MutationObserver((mutationList) => {
|
||||||
|
timeout && clearTimeout(timeout);
|
||||||
|
timeout = window.setTimeout(checkHeader, 2000);
|
||||||
|
});
|
||||||
|
observer.observe($header, { subtree: true, childList: true });
|
||||||
|
checkHeader();
|
||||||
|
}
|
||||||
|
|
||||||
// src/utils/history.ts
|
// src/utils/history.ts
|
||||||
function patchHistoryMethod(type) {
|
function patchHistoryMethod(type) {
|
||||||
|
@ -7,3 +7,11 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
|
|||||||
background-color: #2d2d2d !important;
|
background-color: #2d2d2d !important;
|
||||||
color: #000 !important;
|
color: #000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bx-stream-refresh-button {
|
||||||
|
top: calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-media-type=tv] .bx-stream-refresh-button {
|
||||||
|
top: calc(var(--gds-focus-borderSize) + 80px) !important;
|
||||||
|
}
|
||||||
|
3
src/assets/svg/refresh.svg
Normal file
3
src/assets/svg/refresh.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
|
||||||
|
<path d="M23.247 12.377h7.247V5.13"/><path d="M23.911 25.663a13.29 13.29 0 0 1-9.119 3.623C7.504 29.286 1.506 23.289 1.506 16S7.504 2.713 14.792 2.713a13.29 13.29 0 0 1 9.395 3.891l6.307 5.772"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 378 B |
@ -67,14 +67,15 @@ const PATCHES = {
|
|||||||
return str.replace(text, text + 'return;');
|
return str.replace(text, text + 'return;');
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set TV layout
|
// Set custom website layout
|
||||||
tvLayout(str: string) {
|
websiteLayout(str: string) {
|
||||||
const text = '?"tv":"default"';
|
const text = '?"tv":"default"';
|
||||||
if (!str.includes(text)) {
|
if (!str.includes(text)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return str.replace(text, '?"tv":"tv"');
|
const layout = getPref(PrefKey.UI_LAYOUT) === 'tv' ? 'tv' : 'default';
|
||||||
|
return str.replace(text, `?"${layout}":"${layout}"`);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Replace "/direct-connect" with "/play"
|
// Replace "/direct-connect" with "/play"
|
||||||
@ -465,7 +466,7 @@ let PATCH_ORDERS: PatchArray = [
|
|||||||
'overrideSettings',
|
'overrideSettings',
|
||||||
'broadcastPollingMode',
|
'broadcastPollingMode',
|
||||||
|
|
||||||
getPref(PrefKey.UI_LAYOUT) === 'tv' && 'tvLayout',
|
getPref(PrefKey.UI_LAYOUT) !== 'default' && 'websiteLayout',
|
||||||
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && 'supportLocalCoOp',
|
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && 'supportLocalCoOp',
|
||||||
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
|
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
|
||||||
|
|
||||||
@ -520,8 +521,14 @@ const ALL_PATCHES = [...PATCH_ORDERS, ...PLAYING_PATCH_ORDERS];
|
|||||||
export class Patcher {
|
export class Patcher {
|
||||||
static #patchFunctionBind() {
|
static #patchFunctionBind() {
|
||||||
const nativeBind = Function.prototype.bind;
|
const nativeBind = Function.prototype.bind;
|
||||||
Function.prototype.bind = function () {
|
Function.prototype.bind = function() {
|
||||||
let valid = false;
|
let valid = false;
|
||||||
|
|
||||||
|
// Looking for these criteria:
|
||||||
|
// - Variable name <= 2 characters
|
||||||
|
// - Has 2 params:
|
||||||
|
// - The first one is null
|
||||||
|
// - The second one is either 0 or a function
|
||||||
if (this.name.length <= 2 && arguments.length === 2 && arguments[0] === null) {
|
if (this.name.length <= 2 && arguments.length === 2 && arguments[0] === null) {
|
||||||
if (arguments[1] === 0 || (typeof arguments[1] === 'function')) {
|
if (arguments[1] === 0 || (typeof arguments[1] === 'function')) {
|
||||||
valid = true;
|
valid = true;
|
||||||
@ -533,6 +540,8 @@ export class Patcher {
|
|||||||
return nativeBind.apply(this, arguments);
|
return nativeBind.apply(this, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PatcherCache.init();
|
||||||
|
|
||||||
if (typeof arguments[1] === 'function') {
|
if (typeof arguments[1] === 'function') {
|
||||||
BxLogger.info(LOG_TAG, 'Restored Function.prototype.bind()');
|
BxLogger.info(LOG_TAG, 'Restored Function.prototype.bind()');
|
||||||
Function.prototype.bind = nativeBind;
|
Function.prototype.bind = nativeBind;
|
||||||
@ -549,23 +558,23 @@ export class Patcher {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static length() { return PATCH_ORDERS.length; };
|
|
||||||
|
|
||||||
static patch(item: [[number], { [key: string]: () => {} }]) {
|
static patch(item: [[number], { [key: string]: () => {} }]) {
|
||||||
|
// !!! Use "caches" as variable name will break touch controller???
|
||||||
// console.log('patch', '-----');
|
// console.log('patch', '-----');
|
||||||
let patchesToCheck: PatchArray;
|
let patchesToCheck: PatchArray;
|
||||||
let appliedPatches;
|
let appliedPatches: PatchArray;
|
||||||
const caches: { [key: string]: string[] } = {};
|
|
||||||
|
const patchesMap: { [key: string]: PatchArray } = {};
|
||||||
|
|
||||||
for (let id in item[1]) {
|
for (let id in item[1]) {
|
||||||
appliedPatches = [];
|
appliedPatches = [];
|
||||||
|
|
||||||
const cachedPatches = PatcherCache.getPatches(id);
|
const cachedPatches = PatcherCache.getPatches(id);
|
||||||
if (cachedPatches) {
|
if (cachedPatches) {
|
||||||
patchesToCheck = cachedPatches;
|
patchesToCheck = cachedPatches.slice(0);
|
||||||
patchesToCheck.push(...PATCH_ORDERS);
|
patchesToCheck.push(...PATCH_ORDERS);
|
||||||
} else {
|
} else {
|
||||||
patchesToCheck = PATCH_ORDERS;
|
patchesToCheck = PATCH_ORDERS.slice(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty patch list
|
// Empty patch list
|
||||||
@ -573,20 +582,21 @@ export class Patcher {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(patchesToCheck);
|
|
||||||
const func = item[1][id];
|
const func = item[1][id];
|
||||||
let str = func.toString();
|
let str = func.toString();
|
||||||
|
|
||||||
// console.log(id, str);
|
let modified = false;
|
||||||
|
|
||||||
for (let groupIndex = 0; groupIndex < patchesToCheck.length; groupIndex++) {
|
|
||||||
const patchName = patchesToCheck[groupIndex];
|
|
||||||
let modified = false;
|
|
||||||
|
|
||||||
|
for (let patchIndex = 0; patchIndex < patchesToCheck.length; patchIndex++) {
|
||||||
|
const patchName = patchesToCheck[patchIndex];
|
||||||
if (appliedPatches.indexOf(patchName) > -1) {
|
if (appliedPatches.indexOf(patchName) > -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!PATCHES[patchName]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Check function against patch
|
// Check function against patch
|
||||||
const patchedStr = PATCHES[patchName].call(null, str);
|
const patchedStr = PATCHES[patchName].call(null, str);
|
||||||
|
|
||||||
@ -602,24 +612,24 @@ export class Patcher {
|
|||||||
appliedPatches.push(patchName);
|
appliedPatches.push(patchName);
|
||||||
|
|
||||||
// Remove patch
|
// Remove patch
|
||||||
patchesToCheck.splice(groupIndex, 1);
|
patchesToCheck.splice(patchIndex, 1);
|
||||||
groupIndex--;
|
patchIndex--;
|
||||||
PATCH_ORDERS = PATCH_ORDERS.filter(item => item != patchName);
|
PATCH_ORDERS = PATCH_ORDERS.filter(item => item != patchName);
|
||||||
|
}
|
||||||
|
|
||||||
// Apply patched functions
|
// Apply patched functions
|
||||||
if (modified) {
|
if (modified) {
|
||||||
item[1][id] = eval(str);
|
item[1][id] = eval(str);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to cache
|
// Save to cache
|
||||||
if (appliedPatches.length) {
|
if (appliedPatches.length) {
|
||||||
caches[id] = appliedPatches;
|
patchesMap[id] = appliedPatches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(caches).length) {
|
if (Object.keys(patchesMap).length) {
|
||||||
PatcherCache.saveToCache(caches);
|
PatcherCache.saveToCache(patchesMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,12 +638,14 @@ export class Patcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PatcherCache {
|
export class PatcherCache {
|
||||||
static #KEY_CACHE = 'better_xcloud_patches_cache';
|
static #KEY_CACHE = 'better_xcloud_patches_cache';
|
||||||
static #KEY_SIGNATURE = 'better_xcloud_patches_cache_signature';
|
static #KEY_SIGNATURE = 'better_xcloud_patches_cache_signature';
|
||||||
|
|
||||||
static #CACHE: any;
|
static #CACHE: any;
|
||||||
|
|
||||||
|
static #isInitialized = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get patch's signature
|
* Get patch's signature
|
||||||
*/
|
*/
|
||||||
@ -647,18 +659,22 @@ class PatcherCache {
|
|||||||
return sig;
|
return sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static clear() {
|
||||||
|
// Clear cache
|
||||||
|
window.localStorage.removeItem(PatcherCache.#KEY_CACHE);
|
||||||
|
PatcherCache.#CACHE = {};
|
||||||
|
}
|
||||||
|
|
||||||
static checkSignature() {
|
static checkSignature() {
|
||||||
const storedSig = window.localStorage.getItem(PatcherCache.#KEY_SIGNATURE) || 0;
|
const storedSig = window.localStorage.getItem(PatcherCache.#KEY_SIGNATURE) || 0;
|
||||||
const currentSig = PatcherCache.#getSignature();
|
const currentSig = PatcherCache.#getSignature();
|
||||||
|
|
||||||
if (currentSig !== parseInt(storedSig as string)) {
|
if (currentSig !== parseInt(storedSig as string)) {
|
||||||
BxLogger.warning(LOG_TAG, 'Signature changed');
|
|
||||||
|
|
||||||
// Clear cache
|
|
||||||
window.localStorage.setItem(PatcherCache.#KEY_CACHE, '{}');
|
|
||||||
|
|
||||||
// Save new signature
|
// Save new signature
|
||||||
|
BxLogger.warning(LOG_TAG, 'Signature changed');
|
||||||
window.localStorage.setItem(PatcherCache.#KEY_SIGNATURE, currentSig.toString());
|
window.localStorage.setItem(PatcherCache.#KEY_SIGNATURE, currentSig.toString());
|
||||||
|
|
||||||
|
PatcherCache.clear();
|
||||||
} else {
|
} else {
|
||||||
BxLogger.info(LOG_TAG, 'Signature unchanged');
|
BxLogger.info(LOG_TAG, 'Signature unchanged');
|
||||||
}
|
}
|
||||||
@ -682,7 +698,7 @@ class PatcherCache {
|
|||||||
return PatcherCache.#CACHE[id];
|
return PatcherCache.#CACHE[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
static saveToCache(subCache: { [key: string]: string[] }) {
|
static saveToCache(subCache: { [key: string]: PatchArray }) {
|
||||||
for (const id in subCache) {
|
for (const id in subCache) {
|
||||||
const patchNames = subCache[id];
|
const patchNames = subCache[id];
|
||||||
|
|
||||||
@ -703,6 +719,13 @@ class PatcherCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static init() {
|
static init() {
|
||||||
|
if (PatcherCache.#isInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PatcherCache.#isInitialized = true;
|
||||||
|
|
||||||
|
PatcherCache.checkSignature();
|
||||||
|
|
||||||
// Read cache from storage
|
// Read cache from storage
|
||||||
PatcherCache.#CACHE = JSON.parse(window.localStorage.getItem(PatcherCache.#KEY_CACHE) || '{}');
|
PatcherCache.#CACHE = JSON.parse(window.localStorage.getItem(PatcherCache.#KEY_CACHE) || '{}');
|
||||||
BxLogger.info(LOG_TAG, PatcherCache.#CACHE);
|
BxLogger.info(LOG_TAG, PatcherCache.#CACHE);
|
||||||
@ -721,11 +744,3 @@ class PatcherCache {
|
|||||||
BxLogger.info(LOG_TAG, PLAYING_PATCH_ORDERS.slice(0));
|
BxLogger.info(LOG_TAG, PLAYING_PATCH_ORDERS.slice(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('readystatechange', e => {
|
|
||||||
if (document.readyState === 'interactive') {
|
|
||||||
PatcherCache.checkSignature();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
PatcherCache.init();
|
|
||||||
|
@ -8,65 +8,6 @@ import { StreamBadges } from "./stream-badges.ts";
|
|||||||
import { StreamStats } from "./stream-stats.ts";
|
import { StreamStats } from "./stream-stats.ts";
|
||||||
|
|
||||||
|
|
||||||
class MouseHoldEvent {
|
|
||||||
#isHolding = false;
|
|
||||||
#timeout?: number | null;
|
|
||||||
|
|
||||||
#$elm;
|
|
||||||
#callback;
|
|
||||||
#duration;
|
|
||||||
|
|
||||||
#onMouseDown(e: MouseEvent | TouchEvent) {
|
|
||||||
const _this = this;
|
|
||||||
this.#isHolding = false;
|
|
||||||
|
|
||||||
this.#timeout && clearTimeout(this.#timeout);
|
|
||||||
this.#timeout = window.setTimeout(() => {
|
|
||||||
_this.#isHolding = true;
|
|
||||||
_this.#callback();
|
|
||||||
}, this.#duration);
|
|
||||||
};
|
|
||||||
|
|
||||||
#onMouseUp(e: MouseEvent | TouchEvent) {
|
|
||||||
this.#timeout && clearTimeout(this.#timeout);
|
|
||||||
this.#timeout = null;
|
|
||||||
|
|
||||||
if (this.#isHolding) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
this.#isHolding = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
#addEventListeners = () => {
|
|
||||||
this.#$elm.addEventListener('mousedown', this.#onMouseDown.bind(this));
|
|
||||||
this.#$elm.addEventListener('click', this.#onMouseUp.bind(this));
|
|
||||||
|
|
||||||
this.#$elm.addEventListener('touchstart', this.#onMouseDown.bind(this));
|
|
||||||
this.#$elm.addEventListener('touchend', this.#onMouseUp.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
#clearEventLiseners = () => {
|
|
||||||
this.#$elm.removeEventListener('mousedown', this.#onMouseDown);
|
|
||||||
this.#$elm.removeEventListener('click', this.#onMouseUp);
|
|
||||||
|
|
||||||
this.#$elm.removeEventListener('touchstart', this.#onMouseDown);
|
|
||||||
this.#$elm.removeEventListener('touchend', this.#onMouseUp);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
constructor($elm: HTMLElement, callback: any, duration=1000) {
|
|
||||||
this.#$elm = $elm;
|
|
||||||
this.#callback = callback;
|
|
||||||
this.#duration = duration;
|
|
||||||
|
|
||||||
this.#addEventListeners();
|
|
||||||
// $elm.clearMouseHoldEventListeners = this.#clearEventLiseners;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function cloneStreamHudButton($orgButton: HTMLElement, label: string, svgIcon: typeof BxIcon) {
|
function cloneStreamHudButton($orgButton: HTMLElement, label: string, svgIcon: typeof BxIcon) {
|
||||||
const $container = $orgButton.cloneNode(true) as HTMLElement;
|
const $container = $orgButton.cloneNode(true) as HTMLElement;
|
||||||
let timeout: number | null;
|
let timeout: number | null;
|
||||||
@ -192,25 +133,39 @@ export function injectStreamMenuButtons() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render badges
|
// Render badges
|
||||||
if ($elm.className.startsWith('StreamMenu')) {
|
if ($elm.className.startsWith('StreamMenu-module__container')) {
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_MENU_SHOWN);
|
BxEvent.dispatch(window, BxEvent.STREAM_MENU_SHOWN);
|
||||||
|
|
||||||
// Hide Quick bar when closing HUD
|
|
||||||
const $btnCloseHud = document.querySelector('button[class*=StreamMenu-module__backButton]');
|
const $btnCloseHud = document.querySelector('button[class*=StreamMenu-module__backButton]');
|
||||||
if (!$btnCloseHud) {
|
if (!$btnCloseHud) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide Quick bar when closing HUD
|
||||||
$btnCloseHud && $btnCloseHud.addEventListener('click', e => {
|
$btnCloseHud && $btnCloseHud.addEventListener('click', e => {
|
||||||
$quickBar.classList.add('bx-gone');
|
$quickBar.classList.add('bx-gone');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get "Quit game" button
|
// Create Refresh button from the Close button
|
||||||
const $btnQuit = $elm.querySelector('div[class^=StreamMenu] > div > button:last-child') as HTMLElement;
|
const $btnRefresh = $btnCloseHud.cloneNode(true) as HTMLElement;
|
||||||
// Hold "Quit game" button to refresh the stream
|
|
||||||
new MouseHoldEvent($btnQuit, () => {
|
// Refresh SVG
|
||||||
|
const $svgRefresh = createSvgIcon(BxIcon.REFRESH);
|
||||||
|
// Copy classes
|
||||||
|
$svgRefresh.setAttribute('class', $btnRefresh.firstElementChild!.getAttribute('class') || '');
|
||||||
|
$svgRefresh.style.fill = 'none';
|
||||||
|
|
||||||
|
$btnRefresh.classList.add('bx-stream-refresh-button');
|
||||||
|
// Remove icon
|
||||||
|
$btnRefresh.removeChild($btnRefresh.firstElementChild!);
|
||||||
|
// Add Refresh icon
|
||||||
|
$btnRefresh.appendChild($svgRefresh);
|
||||||
|
// Add "click" event listener
|
||||||
|
$btnRefresh.addEventListener('click', e => {
|
||||||
confirm(t('confirm-reload-stream')) && window.location.reload();
|
confirm(t('confirm-reload-stream')) && window.location.reload();
|
||||||
}, 1000);
|
});
|
||||||
|
// Add to website
|
||||||
|
$btnCloseHud.insertAdjacentElement('afterend', $btnRefresh);
|
||||||
|
|
||||||
// Render stream badges
|
// Render stream badges
|
||||||
const $menu = document.querySelector('div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]');
|
const $menu = document.querySelector('div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]');
|
||||||
|
@ -178,9 +178,7 @@ export class TouchController {
|
|||||||
layout: {
|
layout: {
|
||||||
id: 'System.Standard',
|
id: 'System.Standard',
|
||||||
displayName: 'System',
|
displayName: 'System',
|
||||||
layoutFile: {
|
layoutFile: layout,
|
||||||
content: layout.content,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, delay);
|
}, delay);
|
||||||
@ -188,7 +186,7 @@ export class TouchController {
|
|||||||
|
|
||||||
static setup() {
|
static setup() {
|
||||||
// Function for testing touch control
|
// Function for testing touch control
|
||||||
window.BX_EXPOSED.test_touch_control = (content: any) => {
|
window.BX_EXPOSED.test_touch_control = (layout: any) => {
|
||||||
const { touch_layout_manager } = window.BX_EXPOSED;
|
const { touch_layout_manager } = window.BX_EXPOSED;
|
||||||
|
|
||||||
touch_layout_manager && touch_layout_manager.changeLayoutForScope({
|
touch_layout_manager && touch_layout_manager.changeLayoutForScope({
|
||||||
@ -198,9 +196,7 @@ export class TouchController {
|
|||||||
layout: {
|
layout: {
|
||||||
id: 'System.Standard',
|
id: 'System.Standard',
|
||||||
displayName: 'Custom',
|
displayName: 'Custom',
|
||||||
layoutFile: {
|
layoutFile: layout,
|
||||||
content: content,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@ import { getPreferredServerRegion } from "@utils/region";
|
|||||||
import { UserAgent, UserAgentProfile } from "@utils/user-agent";
|
import { UserAgent, UserAgentProfile } from "@utils/user-agent";
|
||||||
import { getPref, Preferences, PrefKey, setPref, toPrefElement } from "@utils/preferences";
|
import { getPref, Preferences, PrefKey, setPref, toPrefElement } from "@utils/preferences";
|
||||||
import { t, refreshCurrentLocale } from "@utils/translation";
|
import { t, refreshCurrentLocale } from "@utils/translation";
|
||||||
|
import { PatcherCache } from "../patcher";
|
||||||
|
|
||||||
const SETTINGS_UI = {
|
const SETTINGS_UI = {
|
||||||
'Better xCloud': {
|
'Better xCloud': {
|
||||||
@ -159,6 +160,9 @@ export function setupSettingsUi() {
|
|||||||
|
|
||||||
$reloadBtnWrapper.classList.remove('bx-gone');
|
$reloadBtnWrapper.classList.remove('bx-gone');
|
||||||
|
|
||||||
|
// Clear PatcherCache;
|
||||||
|
PatcherCache.clear();
|
||||||
|
|
||||||
if ((e.target as HTMLElement).id === 'bx_setting_' + PrefKey.BETTER_XCLOUD_LOCALE) {
|
if ((e.target as HTMLElement).id === 'bx_setting_' + PrefKey.BETTER_XCLOUD_LOCALE) {
|
||||||
// Update locale
|
// Update locale
|
||||||
refreshCurrentLocale();
|
refreshCurrentLocale();
|
||||||
|
@ -73,9 +73,9 @@ export const BxExposed = {
|
|||||||
|
|
||||||
// Pre-check supported input types
|
// Pre-check supported input types
|
||||||
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
|
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
|
||||||
titleInfo.details.hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) &&
|
titleInfo.details.hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) ||
|
||||||
!supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) &&
|
supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) ||
|
||||||
!supportedInputTypes.includes(InputType.GENERIC_TOUCH);
|
supportedInputTypes.includes(InputType.GENERIC_TOUCH);
|
||||||
|
|
||||||
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === 'all') {
|
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === 'all') {
|
||||||
// Add generic touch support for non touch-supported games
|
// Add generic touch support for non touch-supported games
|
||||||
|
@ -6,6 +6,7 @@ import iconMouseSettings from "@assets/svg/mouse-settings.svg" with { type: "tex
|
|||||||
import iconMouse from "@assets/svg/mouse.svg" with { type: "text" };
|
import iconMouse from "@assets/svg/mouse.svg" with { type: "text" };
|
||||||
import iconNew from "@assets/svg/new.svg" with { type: "text" };
|
import iconNew from "@assets/svg/new.svg" with { type: "text" };
|
||||||
import iconQuestion from "@assets/svg/question.svg" with { type: "text" };
|
import iconQuestion from "@assets/svg/question.svg" with { type: "text" };
|
||||||
|
import iconRefresh from "@assets/svg/refresh.svg" with { type: "text" };
|
||||||
import iconRemotePlay from "@assets/svg/remote-play.svg" with { type: "text" };
|
import iconRemotePlay from "@assets/svg/remote-play.svg" with { type: "text" };
|
||||||
import iconStreamSettings from "@assets/svg/stream-settings.svg" with { type: "text" };
|
import iconStreamSettings from "@assets/svg/stream-settings.svg" with { type: "text" };
|
||||||
import iconStreamStats from "@assets/svg/stream-stats.svg" with { type: "text" };
|
import iconStreamStats from "@assets/svg/stream-stats.svg" with { type: "text" };
|
||||||
@ -23,6 +24,7 @@ export const BxIcon = {
|
|||||||
TRASH: iconTrash,
|
TRASH: iconTrash,
|
||||||
CURSOR_TEXT: iconCursorText,
|
CURSOR_TEXT: iconCursorText,
|
||||||
QUESTION: iconQuestion,
|
QUESTION: iconQuestion,
|
||||||
|
REFRESH: iconRefresh,
|
||||||
|
|
||||||
REMOTE_PLAY: iconRemotePlay,
|
REMOTE_PLAY: iconRemotePlay,
|
||||||
|
|
||||||
|
@ -417,6 +417,7 @@ export class Preferences {
|
|||||||
default: 'default',
|
default: 'default',
|
||||||
options: {
|
options: {
|
||||||
default: t('default'),
|
default: t('default'),
|
||||||
|
normal: t('normal'),
|
||||||
tv: t('smart-tv'),
|
tv: t('smart-tv'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -441,7 +442,9 @@ export class Preferences {
|
|||||||
[UserAgentProfile.DEFAULT]: t('default'),
|
[UserAgentProfile.DEFAULT]: t('default'),
|
||||||
[UserAgentProfile.EDGE_WINDOWS]: 'Edge + Windows',
|
[UserAgentProfile.EDGE_WINDOWS]: 'Edge + Windows',
|
||||||
[UserAgentProfile.SAFARI_MACOS]: 'Safari + macOS',
|
[UserAgentProfile.SAFARI_MACOS]: 'Safari + macOS',
|
||||||
|
[UserAgentProfile.SMARTTV]: 'Smart TV',
|
||||||
[UserAgentProfile.SMARTTV_TIZEN]: 'Samsung Smart TV',
|
[UserAgentProfile.SMARTTV_TIZEN]: 'Samsung Smart TV',
|
||||||
|
[UserAgentProfile.VR_OCULUS]: 'Meta Quest VR',
|
||||||
[UserAgentProfile.KIWI_V123]: 'Kiwi Browser v123',
|
[UserAgentProfile.KIWI_V123]: 'Kiwi Browser v123',
|
||||||
[UserAgentProfile.CUSTOM]: t('custom'),
|
[UserAgentProfile.CUSTOM]: t('custom'),
|
||||||
},
|
},
|
||||||
|
@ -3,7 +3,9 @@ import { PrefKey, getPref } from "@utils/preferences";
|
|||||||
export enum UserAgentProfile {
|
export enum UserAgentProfile {
|
||||||
EDGE_WINDOWS = 'edge-windows',
|
EDGE_WINDOWS = 'edge-windows',
|
||||||
SAFARI_MACOS = 'safari-macos',
|
SAFARI_MACOS = 'safari-macos',
|
||||||
|
SMARTTV = 'smarttv',
|
||||||
SMARTTV_TIZEN = 'smarttv-tizen',
|
SMARTTV_TIZEN = 'smarttv-tizen',
|
||||||
|
VR_OCULUS = 'vr-oculus',
|
||||||
KIWI_V123 = 'kiwi-v123',
|
KIWI_V123 = 'kiwi-v123',
|
||||||
DEFAULT = 'default',
|
DEFAULT = 'default',
|
||||||
CUSTOM = 'custom',
|
CUSTOM = 'custom',
|
||||||
@ -26,7 +28,9 @@ export class UserAgent {
|
|||||||
static #USER_AGENTS = {
|
static #USER_AGENTS = {
|
||||||
[UserAgentProfile.EDGE_WINDOWS]: EDGE_USER_AGENT,
|
[UserAgentProfile.EDGE_WINDOWS]: EDGE_USER_AGENT,
|
||||||
[UserAgentProfile.SAFARI_MACOS]: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1',
|
[UserAgentProfile.SAFARI_MACOS]: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1',
|
||||||
|
[UserAgentProfile.SMARTTV]: window.navigator.userAgent + ' SmartTV',
|
||||||
[UserAgentProfile.SMARTTV_TIZEN]: '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',
|
[UserAgentProfile.SMARTTV_TIZEN]: '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',
|
||||||
|
[UserAgentProfile.VR_OCULUS]: window.navigator.userAgent + ' OculusBrowser VR',
|
||||||
[UserAgentProfile.KIWI_V123]: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.118 Mobile Safari/537.36',
|
[UserAgentProfile.KIWI_V123]: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.118 Mobile Safari/537.36',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user