Compare commits

...

54 Commits

Author SHA1 Message Date
f7f01fd27e Bump version to 6.6.2 2025-06-06 07:30:46 +07:00
67c2fb125f Fix "ignoreNewsSection" patch 2025-06-06 07:29:53 +07:00
fad91d14a6 Fix "patchPollGamepads" patch 2025-06-06 07:27:34 +07:00
3a3fc77e83 Fix virtual controller stopped working (#731) 2025-06-06 07:25:11 +07:00
4bf3bd3bb4 Use findAndParseParams() in "gameCardCustomIcons" patch 2025-05-31 11:30:21 +07:00
cc33e27bd6 Fix "ignoreSiglSections" patch 2025-05-31 11:16:34 +07:00
9112853dfc Update stale.yaml 2025-05-31 09:59:55 +07:00
368164b567 Create stale.yaml 2025-05-31 09:58:13 +07:00
ce71c3043e Bump version to 6.6.1 2025-05-29 21:03:52 +07:00
3bb138cd05 Fix "injectErrorPageUseEffect" patch (not showing Error page) 2025-05-29 20:59:31 +07:00
3d6688e1db Set script version to 6.6.1-beta 2025-05-29 20:56:39 +07:00
fc354e680c Upgrade bun 2025-05-29 20:56:25 +07:00
be51199279 Fix built scripts 2025-05-29 20:54:17 +07:00
e276d9a2b9 Fix "disableGamepadDisconnectedScreen" patch 2025-05-29 20:51:30 +07:00
c2f9f129d0 Bump version to 6.6.0 2025-05-29 08:50:09 +07:00
aa50261726 Update translations 2025-05-29 08:49:52 +07:00
bb32d97ae8 Fix "disableTouchContextMenu" patch 2025-05-29 08:44:16 +07:00
3d2abf6b12 Fix "enableTvRoutes" patch 2025-05-29 08:39:06 +07:00
4c8a49a43a Fix "injectErrorPageUseEffect" patch 2025-05-29 08:38:41 +07:00
256f28695e Fix "injectErrorPageUseEffect" patch 2025-05-29 08:27:20 +07:00
9e851fbd15 Fix "skipFeedbackDialog" patch 2025-05-29 08:21:36 +07:00
c829f74dcc Fix "patchStreamHud" patch 2025-05-29 08:16:29 +07:00
62cf045f05 Fix "ignorePlayWithFriendsSection" patch 2025-05-29 07:53:13 +07:00
fdb4e58b5d Fix "gameCardCustomIcons" patch 2025-05-29 07:47:32 +07:00
b1407c2447 Update translations 2025-05-29 07:34:25 +07:00
b5ba6e9600 Fix web's version detection 2025-05-29 07:34:07 +07:00
a3094d2c9f Set Performance mode before Quality mode 2025-05-14 17:59:44 +07:00
3290a36886 Add Clarity boost mode 2025-05-14 17:21:50 +07:00
e502e49d64 Bump version to 6.5.0 2025-04-26 08:30:53 +07:00
604cf7094a Update translations 2025-04-26 08:23:47 +07:00
3bfa7e5f21 Inject Better xCloud button to Remote Play page's header 2025-04-26 08:22:20 +07:00
e3789b4fb7 Add webOS and xboxOS to the list of OS names 2025-04-26 07:47:24 +07:00
0551d909e5 Support new screen ratios: 20:9, 3:2, 5:4 2025-04-21 07:15:51 +07:00
da6ab51ba0 Refactor Remote Play feature 2025-04-20 20:28:48 +07:00
4a65221ad0 Upgrade bun 2025-04-20 15:29:25 +07:00
528c6774fe Bump version to 6.4.10 2025-04-16 16:43:40 +07:00
3c8a35d441 Fix "disableTouchContextMenu" patch 2025-04-16 16:42:59 +07:00
544ededb64 Bump version to 6.4.9 2025-04-15 04:52:46 +07:00
f4f88f688b Fix script running on non-xCloud pages (#698) 2025-04-15 04:52:21 +07:00
1fb1a64767 Fix Remote Play stopped working with xCloud 29.1.60 2025-04-15 04:39:50 +07:00
769649a376 Upgrade bun 2025-04-15 04:36:54 +07:00
057adb62df Bump version to 6.4.8 2025-03-27 07:36:23 +07:00
98e8ff4783 Fix header's style in other pages 2025-03-27 07:26:05 +07:00
f5e1b0a9fa Upgrade bun 2025-03-27 07:19:50 +07:00
8ea3503dd3 Update header's style for small screens 2025-03-27 07:19:08 +07:00
b733d55e9e Bump version to 6.4.7 2025-03-21 06:37:13 +07:00
317ac9017b Fix custom input icons not showing in game card 2025-03-21 06:28:59 +07:00
b8c62a1f4d Fix Remote Play's achievement notification 2025-03-21 05:37:38 +07:00
7332528f72 Remove "enableConsoleLogging" patch from Stream page 2025-03-21 05:29:14 +07:00
d063500aae Fix not detecting new xCloud version correctly 2025-03-21 05:26:09 +07:00
29ff1bc09c Bump version to 6.4.6 2025-03-11 17:49:46 +07:00
8998daf14c Always check for new version 2025-03-11 17:49:15 +07:00
8bdad8b319 Add Czech translations 2025-03-11 17:43:49 +07:00
5dd3ebdea1 Fix unable to connect to console using Remote Play in some cases 2025-03-11 17:27:59 +07:00
38 changed files with 671 additions and 407 deletions

25
.github/workflows/stale.yaml vendored Normal file
View File

@ -0,0 +1,25 @@
name: 'Mark stale bug issues'
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # Runs daily at midnight UTC
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: |
This issue has been marked `stale` due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days.
close-issue-message: |
This issue has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details.
days-before-stale: 60
days-before-close: 30
stale-issue-label: 'stale'
only-issues: true
only-labels: 'bug'

View File

@ -3,11 +3,11 @@
"workspaces": { "workspaces": {
"": { "": {
"devDependencies": { "devDependencies": {
"@types/bun": "^1.2.0", "@types/bun": "^1.2.10",
"@types/node": "^22.10.10", "@types/node": "^22.14.1",
"@types/stylus": "^0.48.43", "@types/stylus": "^0.48.43",
"@webgpu/types": "^0.1.53", "@webgpu/types": "^0.1.60",
"eslint": "^9.19.0", "eslint": "^9.25.0",
"eslint-plugin-compat": "^6.0.2", "eslint-plugin-compat": "^6.0.2",
"stylus": "^0.64.0", "stylus": "^0.64.0",
}, },
@ -23,17 +23,19 @@
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
"@eslint/config-array": ["@eslint/config-array@0.19.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.4", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ=="], "@eslint/config-array": ["@eslint/config-array@0.20.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ=="],
"@eslint/core": ["@eslint/core@0.11.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA=="], "@eslint/config-helpers": ["@eslint/config-helpers@0.2.1", "", {}, "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.2.0", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w=="], "@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="],
"@eslint/js": ["@eslint/js@9.20.0", "", {}, "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ=="], "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
"@eslint/object-schema": ["@eslint/object-schema@2.1.4", "", {}, "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ=="], "@eslint/js": ["@eslint/js@9.27.0", "", {}, "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA=="],
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.5", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="], "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
@ -41,7 +43,7 @@
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.1", "", {}, "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA=="], "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.2", "", {}, "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
@ -49,19 +51,17 @@
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="], "@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="],
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="], "@types/node": ["@types/node@22.15.24", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng=="],
"@types/stylus": ["@types/stylus@0.48.43", "", { "dependencies": { "@types/node": "*" } }, "sha512-72dv/zdhuyXWVHUXG2VTPEQdOG+oen95/DNFx2aMFFaY6LoITI6PwEqf5x31JF49kp2w9hvUzkNfTGBIeg61LQ=="], "@types/stylus": ["@types/stylus@0.48.43", "", { "dependencies": { "@types/node": "*" } }, "sha512-72dv/zdhuyXWVHUXG2VTPEQdOG+oen95/DNFx2aMFFaY6LoITI6PwEqf5x31JF49kp2w9hvUzkNfTGBIeg61LQ=="],
"@types/ws": ["@types/ws@8.5.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A=="], "@webgpu/types": ["@webgpu/types@0.1.61", "", {}, "sha512-w2HbBvH+qO19SB5pJOJFKs533CdZqxl3fcGonqL321VHkW7W/iBo6H8bjDy6pr/+pbMwIu5dnuaAxH7NxBqUrQ=="],
"@webgpu/types": ["@webgpu/types@0.1.54", "", {}, "sha512-81oaalC8LFrXjhsczomEQ0u3jG+TqE6V9QHLA8GNZq/Rnot0KDugu3LhSYSlie8tSdooAN1Hov05asrUUp9qgg=="],
"acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], "acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
@ -83,7 +83,7 @@
"browserslist": ["browserslist@4.24.3", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA=="], "browserslist": ["browserslist@4.24.3", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA=="],
"bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="], "bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
@ -113,11 +113,11 @@
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@9.20.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.11.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.20.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-aL4F8167Hg4IvsW89ejnpTwx+B/UQRzJPGgbIOl+4XqffWsahVVsLEWoZvnrVuwpWmnRd7XeXmQI1zlKcFDteA=="], "eslint": ["eslint@9.27.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.27.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q=="],
"eslint-plugin-compat": ["eslint-plugin-compat@6.0.2", "", { "dependencies": { "@mdn/browser-compat-data": "^5.5.35", "ast-metadata-inferer": "^0.8.1", "browserslist": "^4.24.2", "caniuse-lite": "^1.0.30001687", "find-up": "^5.0.0", "globals": "^15.7.0", "lodash.memoize": "^4.1.2", "semver": "^7.6.2" }, "peerDependencies": { "eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-1ME+YfJjmOz1blH0nPZpHgjMGK4kjgEeoYqGCqoBPQ/mGu/dJzdoP0f1C8H2jcWZjzhZjAMccbM/VdXhPORIfA=="], "eslint-plugin-compat": ["eslint-plugin-compat@6.0.2", "", { "dependencies": { "@mdn/browser-compat-data": "^5.5.35", "ast-metadata-inferer": "^0.8.1", "browserslist": "^4.24.2", "caniuse-lite": "^1.0.30001687", "find-up": "^5.0.0", "globals": "^15.7.0", "lodash.memoize": "^4.1.2", "semver": "^7.6.2" }, "peerDependencies": { "eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-1ME+YfJjmOz1blH0nPZpHgjMGK4kjgEeoYqGCqoBPQ/mGu/dJzdoP0f1C8H2jcWZjzhZjAMccbM/VdXhPORIfA=="],
"eslint-scope": ["eslint-scope@8.2.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A=="], "eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="],
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
@ -255,7 +255,7 @@
"typescript": ["typescript@5.7.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg=="], "typescript": ["typescript@5.7.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"update-browserslist-db": ["update-browserslist-db@1.1.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.0" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A=="], "update-browserslist-db": ["update-browserslist-db@1.1.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.0" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A=="],
@ -275,14 +275,10 @@
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.10.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw=="],
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
"@types/stylus/@types/node": ["@types/node@22.5.5", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA=="], "@types/stylus/@types/node": ["@types/node@22.5.5", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA=="],
"@types/ws/@types/node": ["@types/node@20.14.2", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q=="],
"ast-metadata-inferer/@mdn/browser-compat-data": ["@mdn/browser-compat-data@5.6.26", "", {}, "sha512-7NdgdOR7lkzrN70zGSULmrcvKyi/aJjpTJRCbuy8IZuHiLkPTvsr10jW0MJgWzK2l2wTmhdQvegTw6yNU5AVNQ=="], "ast-metadata-inferer/@mdn/browser-compat-data": ["@mdn/browser-compat-data@5.6.26", "", {}, "sha512-7NdgdOR7lkzrN70zGSULmrcvKyi/aJjpTJRCbuy8IZuHiLkPTvsr10jW0MJgWzK2l2wTmhdQvegTw6yNU5AVNQ=="],
"foreground-child/cross-spawn": ["cross-spawn@7.0.3", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w=="], "foreground-child/cross-spawn": ["cross-spawn@7.0.3", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w=="],
@ -303,8 +299,6 @@
"@types/stylus/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], "@types/stylus/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
"@types/ws/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], "glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],

View File

@ -1,5 +1,5 @@
// ==UserScript== // ==UserScript==
// @name Better xCloud // @name Better xCloud
// @namespace https://github.com/redphx // @namespace https://github.com/redphx
// @version 6.4.5 // @version 6.6.2
// ==/UserScript== // ==/UserScript==

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,11 +10,11 @@
"build": "build.ts" "build": "build.ts"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "^1.2.2", "@types/bun": "^1.2.15",
"@types/node": "^22.13.1", "@types/node": "^22.15.24",
"@types/stylus": "^0.48.43", "@types/stylus": "^0.48.43",
"@webgpu/types": "^0.1.54", "@webgpu/types": "^0.1.61",
"eslint": "^9.20.0", "eslint": "^9.27.0",
"eslint-plugin-compat": "^6.0.2", "eslint-plugin-compat": "^6.0.2",
"stylus": "^0.64.0" "stylus": "^0.64.0"
}, },

View File

@ -60,9 +60,35 @@ button_color(name, normal, hover, active, disabled)
} }
/* Remove the "Cloud Gaming" text in header when the screen is too small */ /* Remove the "Cloud Gaming" text in header when the screen is too small */
@media screen and (min-width: 641px) and (max-width: 767px) {
header {
button[class^="ExperienceDropdown-module__toggleButton"],
button[class^="XboxButton-module__headerXboxButton"] {
margin-right: 10px !important;
}
a[href="/play"],
button[class^="ExperienceDropdown-module__toggleButton"] {
> div {
> div {
font-size: 12px;
}
> svg {
width: 20px;
height: 20px;
}
}
}
}
}
@media screen and (max-width: 640px) { @media screen and (max-width: 640px) {
header a[href="/play"] { header {
display: none; a[href="/play"],
button[class^="ExperienceDropdown-module__toggleButton"] {
display: none;
}
} }
} }

View File

@ -7,6 +7,7 @@
// @license MIT // @license MIT
// @match https://www.xbox.com/*/play* // @match https://www.xbox.com/*/play*
// @match https://www.xbox.com/*/auth/msa?*loggedIn* // @match https://www.xbox.com/*/auth/msa?*loggedIn*
// @exclude https://www.xbox.com/*/xbox-game-pass/play-day-one
// @run-at document-start // @run-at document-start
// @grant none // @grant none
// @updateURL https://raw.githubusercontent.com/redphx/better-xcloud/typescript/dist/better-xcloud.meta.js // @updateURL https://raw.githubusercontent.com/redphx/better-xcloud/typescript/dist/better-xcloud.meta.js

View File

@ -1,5 +1,5 @@
import type { BaseSettingsStorage } from "@/utils/settings-storages/base-settings-storage"; import type { BaseSettingsStorage } from "@/utils/settings-storages/base-settings-storage";
import type { BlockFeature, CodecProfile, DeviceVibrationMode, GameBarPosition, LoadingScreenRocket, NativeMkbMode, StreamPlayerType, StreamResolution, StreamStat, StreamStatPosition, StreamVideoProcessing, TouchControllerMode, TouchControllerStyleCustom, TouchControllerStyleStandard, UiLayout, UiSection, UiTheme, VideoPosition, VideoPowerPreference, VideoRatio } from "./pref-values" import type { BlockFeature, CodecProfile, DeviceVibrationMode, GameBarPosition, LoadingScreenRocket, NativeMkbMode, StreamPlayerType, StreamResolution, StreamStat, StreamStatPosition, StreamVideoProcessing, StreamVideoProcessingMode, TouchControllerMode, TouchControllerStyleCustom, TouchControllerStyleStandard, UiLayout, UiSection, UiTheme, VideoPosition, VideoPowerPreference, VideoRatio } from "./pref-values"
export const enum StorageKey { export const enum StorageKey {
GLOBAL = 'BetterXcloud', GLOBAL = 'BetterXcloud',
@ -79,7 +79,6 @@ export const enum GlobalPref {
AUDIO_MIC_ON_PLAYING = 'audio.mic.onPlaying', AUDIO_MIC_ON_PLAYING = 'audio.mic.onPlaying',
AUDIO_VOLUME_CONTROL_ENABLED = 'audio.volume.booster.enabled', AUDIO_VOLUME_CONTROL_ENABLED = 'audio.volume.booster.enabled',
REMOTE_PLAY_ENABLED = 'xhome.enabled',
REMOTE_PLAY_STREAM_RESOLUTION = 'xhome.video.resolution', REMOTE_PLAY_STREAM_RESOLUTION = 'xhome.video.resolution',
GAME_FORTNITE_FORCE_CONSOLE = 'game.fortnite.forceConsole', GAME_FORTNITE_FORCE_CONSOLE = 'game.fortnite.forceConsole',
@ -99,7 +98,6 @@ export type GlobalPrefTypeMap = {
[GlobalPref.MKB_HIDE_IDLE_CURSOR]: boolean; [GlobalPref.MKB_HIDE_IDLE_CURSOR]: boolean;
[GlobalPref.NATIVE_MKB_FORCED_GAMES]: string[]; [GlobalPref.NATIVE_MKB_FORCED_GAMES]: string[];
[GlobalPref.NATIVE_MKB_MODE]: NativeMkbMode; [GlobalPref.NATIVE_MKB_MODE]: NativeMkbMode;
[GlobalPref.REMOTE_PLAY_ENABLED]: boolean;
[GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION]: StreamResolution; [GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION]: StreamResolution;
[GlobalPref.SCREENSHOT_APPLY_FILTERS]: boolean; [GlobalPref.SCREENSHOT_APPLY_FILTERS]: boolean;
[GlobalPref.SERVER_BYPASS_RESTRICTION]: string; [GlobalPref.SERVER_BYPASS_RESTRICTION]: string;
@ -158,6 +156,7 @@ export const enum StreamPref {
VIDEO_PLAYER_TYPE = 'video.player.type', VIDEO_PLAYER_TYPE = 'video.player.type',
VIDEO_POWER_PREFERENCE = 'video.player.powerPreference', VIDEO_POWER_PREFERENCE = 'video.player.powerPreference',
VIDEO_PROCESSING = 'video.processing', VIDEO_PROCESSING = 'video.processing',
VIDEO_PROCESSING_MODE = 'video.processing.mode',
VIDEO_SHARPNESS = 'video.processing.sharpness', VIDEO_SHARPNESS = 'video.processing.sharpness',
VIDEO_MAX_FPS = 'video.maxFps', VIDEO_MAX_FPS = 'video.maxFps',
VIDEO_RATIO = 'video.ratio', VIDEO_RATIO = 'video.ratio',
@ -207,6 +206,7 @@ export type StreamPrefTypeMap = {
[StreamPref.VIDEO_POSITION]: VideoPosition; [StreamPref.VIDEO_POSITION]: VideoPosition;
[StreamPref.VIDEO_POWER_PREFERENCE]: VideoPowerPreference; [StreamPref.VIDEO_POWER_PREFERENCE]: VideoPowerPreference;
[StreamPref.VIDEO_PROCESSING]: StreamVideoProcessing; [StreamPref.VIDEO_PROCESSING]: StreamVideoProcessing;
[StreamPref.VIDEO_PROCESSING_MODE]: StreamVideoProcessingMode;
[StreamPref.VIDEO_RATIO]: VideoRatio; [StreamPref.VIDEO_RATIO]: VideoRatio;
[StreamPref.VIDEO_SATURATION]: number; [StreamPref.VIDEO_SATURATION]: number;
[StreamPref.VIDEO_SHARPNESS]: number; [StreamPref.VIDEO_SHARPNESS]: number;
@ -232,7 +232,6 @@ export const ALL_PREFS: {
GlobalPref.MKB_HIDE_IDLE_CURSOR, GlobalPref.MKB_HIDE_IDLE_CURSOR,
GlobalPref.NATIVE_MKB_FORCED_GAMES, GlobalPref.NATIVE_MKB_FORCED_GAMES,
GlobalPref.NATIVE_MKB_MODE, GlobalPref.NATIVE_MKB_MODE,
GlobalPref.REMOTE_PLAY_ENABLED,
GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION,
GlobalPref.SCREENSHOT_APPLY_FILTERS, GlobalPref.SCREENSHOT_APPLY_FILTERS,
GlobalPref.SERVER_BYPASS_RESTRICTION, GlobalPref.SERVER_BYPASS_RESTRICTION,
@ -297,6 +296,7 @@ export const ALL_PREFS: {
StreamPref.VIDEO_POSITION, StreamPref.VIDEO_POSITION,
StreamPref.VIDEO_POWER_PREFERENCE, StreamPref.VIDEO_POWER_PREFERENCE,
StreamPref.VIDEO_PROCESSING, StreamPref.VIDEO_PROCESSING,
StreamPref.VIDEO_PROCESSING_MODE,
StreamPref.VIDEO_RATIO, StreamPref.VIDEO_RATIO,
StreamPref.VIDEO_SATURATION, StreamPref.VIDEO_SATURATION,
StreamPref.VIDEO_SHARPNESS, StreamPref.VIDEO_SHARPNESS,

View File

@ -95,10 +95,13 @@ export const enum StreamStatPosition {
export const enum VideoRatio { export const enum VideoRatio {
'16:9' = '16:9', '16:9' = '16:9',
'18:9' = '18:9',
'21:9' = '21:9',
'16:10' = '16:10', '16:10' = '16:10',
'18:9' = '18:9',
'20:9' = '20:9',
'21:9' = '21:9',
'3:2' = '3:2',
'4:3' = '4:3', '4:3' = '4:3',
'5:4' = '5:4',
FILL = 'fill', FILL = 'fill',
} }
@ -127,12 +130,18 @@ export const enum StreamVideoProcessing {
CAS = 'cas', CAS = 'cas',
} }
export const enum StreamVideoProcessingMode {
QUALITY = 'quality',
PERFORMANCE = 'performance',
}
export const enum BlockFeature { export const enum BlockFeature {
CHAT = 'chat', CHAT = 'chat',
FRIENDS = 'friends', FRIENDS = 'friends',
BYOG = 'byog', BYOG = 'byog',
NOTIFICATIONS_INVITES = 'notifications-invites', NOTIFICATIONS_INVITES = 'notifications-invites',
NOTIFICATIONS_ACHIEVEMENTS = 'notifications-achievements', NOTIFICATIONS_ACHIEVEMENTS = 'notifications-achievements',
REMOTE_PLAY = 'remote-play',
} }
export const enum UiTheme { export const enum UiTheme {

View File

@ -147,6 +147,12 @@ if (isFullVersion() && BX_FLAGS.SafariWorkaround && document.readyState !== 'loa
throw new Error('[Better xCloud] Executing workaround for Safari'); throw new Error('[Better xCloud] Executing workaround for Safari');
} }
// Make sure it only run on /play
if (!window.location.pathname.match(/^\/[a-zA-Z]{2}-[a-zA-Z]{2}\/play/)) {
throw new Error('[Better xCloud] Not xCloud page');
}
window.addEventListener('load', e => { window.addEventListener('load', e => {
// Automatically reload the page when running into the "We are sorry..." error message // Automatically reload the page when running into the "We are sorry..." error message
window.setTimeout(() => { window.setTimeout(() => {
@ -323,7 +329,7 @@ BxEventBus.Stream.on('dataChannelCreated', payload => {
let newId: number = parseInt(json.titleid, 16); let newId: number = parseInt(json.titleid, 16);
// Get titleSlug for Remote Play // Get titleSlug for Remote Play
if (STATES.remotePlay.isPlaying) { if (window.location.pathname.includes('/play/consoles/launch/')) {
currentStream.titleSlug = 'remote-play'; currentStream.titleSlug = 'remote-play';
if (json.focused) { if (json.focused) {
const productTitle = await XboxApi.getProductTitle(newId); const productTitle = await XboxApi.getProductTitle(newId);
@ -442,11 +448,6 @@ function main() {
Patcher.init(); Patcher.init();
disablePwa(); disablePwa();
// Preload Remote Play
if (getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED)) {
RemotePlayManager.detect();
}
if (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL) { if (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL) {
TouchController.setup(); TouchController.setup();
} }

View File

@ -36,18 +36,24 @@ export class PatcherUtils {
return txt.substring(0, index) + toString + txt.substring(index + fromString.length); return txt.substring(0, index) + toString + txt.substring(index + fromString.length);
} }
static replaceAfterIndex(txt: string, search: string, replaceWith: string, index: number) {
const before = txt.slice(0, index);
const after = txt.slice(index).replace(search, replaceWith);
return before + after;
}
static filterPatches(patches: Array<PatchName | false>): PatchArray { static filterPatches(patches: Array<PatchName | false>): PatchArray {
return patches.filter((item): item is PatchName => !!item); return patches.filter((item): item is PatchName => !!item);
} }
static patchBeforePageLoad(str: string, page: PatchPage): string | false { static patchBeforePageLoad(str: string, page: PatchPage): string | false {
let text = `chunkName:()=>"${page}-page",`; const index = str.indexOf(`chunkName:()=>"${page}-page",`);
if (!str.includes(text)) { if (index < 0) {
return false; return false;
} }
str = str.replace('requireAsync(e){', `requireAsync(e){window.BX_EXPOSED.beforePageLoad("${page}");`); str = PatcherUtils.replaceAfterIndex(str, 'requireAsync(e){', `requireAsync(e){window.BX_EXPOSED.beforePageLoad("${page}");`, index);
str = str.replace('requireSync(e){', `requireSync(e){window.BX_EXPOSED.beforePageLoad("${page}");`); str = PatcherUtils.replaceAfterIndex(str, 'requireSync(e){', `requireSync(e){window.BX_EXPOSED.beforePageLoad("${page}");`, index);
return str; return str;
} }
@ -98,10 +104,47 @@ export class PatcherUtils {
return str.substring(start, end); return str.substring(start, end);
} }
static injectUseEffect<T extends 'Stream' | 'Script'>(str: string, index: number, group: T, eventName: T extends 'Stream' ? keyof StreamEvents : keyof ScriptEvents) { static injectUseEffect<T extends 'Stream' | 'Script'>(str: string, index: number, group: T, eventName: T extends 'Stream' ? keyof StreamEvents : keyof ScriptEvents, separator: string = ';') {
const newCode = `window.BX_EXPOSED.reactUseEffect(() => window.BxEventBus.${group}.emit('${eventName}', {}), []);`; const newCode = `window.BX_EXPOSED.reactUseEffect(() => window.BxEventBus.${group}.emit('${eventName}', {}), [])${separator}`;
str = PatcherUtils.insertAt(str, index, newCode); str = PatcherUtils.insertAt(str, index, newCode);
return str; return str;
} }
static findAndParseParams(str: string, index: number, maxRange: number) {
const substr = str.substring(index, index + maxRange);
let startIndex = substr.indexOf('({');
if (startIndex < 0) {
return false;
}
startIndex += 1;
let endIndex = substr.indexOf('})', startIndex);
if (endIndex < 0) {
return false;
}
endIndex += 1;
try {
const input = substr.substring(startIndex, endIndex);
return PatcherUtils.parseObjectVariables(input);
} catch {
return null;
}
}
static parseObjectVariables(input: string) {
try {
const pairs = [...input.matchAll(/(\w+)\s*:\s*([a-zA-Z_$][\w$]*)/g)];
const result: Record<string, string> = {};
for (const [_, key, value] of pairs) {
result[key] = value;
}
return result;
} catch {
return null;
}
}
} }

View File

@ -21,7 +21,7 @@ import { PatcherUtils } from "./patcher-utils.js";
export type PatchName = keyof typeof PATCHES; export type PatchName = keyof typeof PATCHES;
export type PatchArray = PatchName[]; export type PatchArray = PatchName[];
export type PatchPage = 'home' | 'stream' | 'product-detail'; export type PatchPage = 'home' | 'stream' | 'remote-play-stream' | 'product-detail';
type PatchFunction = (str: string) => string | false; type PatchFunction = (str: string) => string | false;
const LOG_TAG = 'Patcher'; const LOG_TAG = 'Patcher';
@ -114,18 +114,6 @@ const PATCHES = {
return str; return str;
}, },
// Enable Remote Play feature
remotePlayConnectMode(str: string) {
let text = 'connectMode:"cloud-connect",';
if (!str.includes(text)) {
return false;
}
const newCode = `connectMode: window.BX_REMOTE_PLAY_CONFIG ? "xhome-connect" : "cloud-connect",
remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFIG.serverId) || '',`;
return str.replace(text, newCode);
},
// Remote Play: Disable achievement toast // Remote Play: Disable achievement toast
remotePlayDisableAchievementToast(str: string) { remotePlayDisableAchievementToast(str: string) {
let text = '.AchievementUnlock:{'; let text = '.AchievementUnlock:{';
@ -133,7 +121,7 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
return false; return false;
} }
const newCode = `if (!!window.BX_REMOTE_PLAY_CONFIG) return;`; const newCode = `if (window.location.pathname.includes('/play/consoles/launch/')) return;`;
return str.replace(text, text + newCode); return str.replace(text, text + newCode);
}, },
@ -148,7 +136,7 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
}, },
patchPollGamepads(str: string) { patchPollGamepads(str: string) {
const index = str.indexOf('},this.pollGamepads=()=>{'); const index = str.indexOf('()(this,"pollGamepads",');
if (index < 0) { if (index < 0) {
return false; return false;
} }
@ -249,8 +237,12 @@ logFunc(logTag, '//', logMessage);
return false; return false;
} }
const constIndex = str.indexOf('const', index - 30); const constIndex = PatcherUtils.lastIndexOf(str, 'const[', index, 100);
str = str.substring(0, constIndex) + 'e.onClose();return null;' + str.substring(constIndex); if (constIndex < 0) {
return false;
}
str = PatcherUtils.insertAt(str, constIndex, 'e();return null;');
return str; return str;
}, },
@ -401,20 +393,40 @@ if (titleInfo && !titleInfo.details.hasTouchSupport && !titleInfo.details.hasFak
}, },
patchStreamHud(str: string) { patchStreamHud(str: string) {
let index = str.indexOf('let{onCollapse'); let index = str.indexOf('({onCollapse:');
if (index < 0) { if (index < 0) {
return false; return false;
} }
let newCode = codeStreamHud; try {
const params = PatcherUtils.findAndParseParams(str, index, 1000);
if (!params) {
return false;
}
// Remove the TAK Edit button when the touch controller is disabled const canShowTakHUDVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'canShowTakHUD', index, 500, true) + 1);
if (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) { const guideUIVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'guideUI', index, 500, true) + 1);
newCode += 'options.canShowTakHUD = false;'; const onShowStreamMenuVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'onShowStreamMenu', index, 500, true) + 1);
const offsetVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'offset', index, 500, true) + 1);
let newCode = renderString(codeStreamHud, {
guideUI: guideUIVar,
onShowStreamMenu: onShowStreamMenuVar,
offset: offsetVar,
});
// Remove the TAK Edit button when the touch controller is disabled
if (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
newCode += `${canShowTakHUDVar} = false;`;
}
const bracketIndex = PatcherUtils.indexOf(str, '}){', index, 500, true);
str = PatcherUtils.insertAt(str, bracketIndex, newCode);
return str;
} catch (e) {
return false;
} }
str = PatcherUtils.insertAt(str, index, newCode);
return str;
}, },
broadcastPollingMode(str: string) { broadcastPollingMode(str: string) {
@ -580,12 +592,13 @@ true` + text;
}, },
skipFeedbackDialog(str: string) { skipFeedbackDialog(str: string) {
let text = 'shouldTransitionToFeedback(e){'; let index = str.indexOf('}shouldTransitionToFeedback(');
if (!str.includes(text)) { index >= 0 && (index = PatcherUtils.indexOf(str, '}){', index, 200, true));
if (index < 0) {
return false; return false;
} }
str = str.replace(text, text + 'return !1;'); str = PatcherUtils.insertAt(str, index, 'return !1;');
return str; return str;
}, },
@ -619,13 +632,12 @@ true` + text;
}, },
exposeInputChannel(str: string) { exposeInputChannel(str: string) {
let index = str.indexOf('this.flushData='); let text = '()(this,"flushData",(';
if (index < 0) { if (!str.includes(text)) {
return false; return false;
} }
const newCode = 'window.BX_EXPOSED.inputChannel = this,'; str = str.replace(text, '()(window.BX_EXPOSED.inputChannel = this, "flushData", (');
str = PatcherUtils.insertAt(str, index, newCode);
return str; return str;
}, },
@ -676,9 +688,9 @@ true` + text;
// Replace *qe*'s return value // Replace *qe*'s return value
// `return a && r ?` => `return a && r || true ?` // `return a && r ?` => `return a && r || true ?`
index = str.indexOf(`const ${funcName}=e=>{`); index = str.indexOf(`const ${funcName}=({children`);
index > -1 && (index = str.indexOf('return ', index)); index > -1 && (index = PatcherUtils.indexOf(str, 'return ', 300));
index > -1 && (index = str.indexOf('?', index)); index > -1 && (index = PatcherUtils.indexOf(str, '?', 100));
if (index < 0) { if (index < 0) {
return false; return false;
@ -690,7 +702,7 @@ true` + text;
// Don't render News section // Don't render News section
ignoreNewsSection(str: string) { ignoreNewsSection(str: string) {
let index = str.indexOf('Logger("CarouselRow")'); let index = str.indexOf('("CarouselRow"))');
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'const ', index, 200)); index > -1 && (index = PatcherUtils.lastIndexOf(str, 'const ', index, 200));
if (index < 0) { if (index < 0) {
return false; return false;
@ -707,12 +719,12 @@ true` + text;
return false; return false;
} }
index = PatcherUtils.lastIndexOf(str, 'return', index, 50); index = PatcherUtils.lastIndexOf(str, '=>', index, 50);
if (index < 0) { if (index < 0) {
return false; return false;
} }
str = PatcherUtils.replaceWith(str, index, 'return', 'return null;'); str = PatcherUtils.replaceWith(str, index, '=>', '=> true ? null :');
return str; return str;
}, },
@ -760,12 +772,13 @@ true` + text;
// home-page.js // home-page.js
ignoreSiglSections(str: string) { ignoreSiglSections(str: string) {
let index = str.indexOf('SiglRow-module__heroCard___'); let index = str.indexOf('SiglRow-module__heroCard___');
index >= 0 && (index = PatcherUtils.lastIndexOf(str, 'const[', index, 300));
if (index < 0) { if (index < 0) {
return false; return false;
} }
index = PatcherUtils.lastIndexOf(str, 'const[', index, 300); const params = PatcherUtils.findAndParseParams(str, index - 500, 500);
if (index < 0) { if (!params || !params.id) {
return false; return false;
} }
@ -784,16 +797,9 @@ true` + text;
galleryId && siglIds.push(galleryId); galleryId && siglIds.push(galleryId);
}; };
const checkSyntax = siglIds.map(item => `siglId === "${item}"`).join(' || '); const checkSyntax = siglIds.map(item => `${params.id} === "${item}"`).join(' || ');
const newCode = `if (${params.id} && (${checkSyntax})) return null;`;
const newCode = `
if (e && e.id) {
const siglId = e.id;
if (${checkSyntax}) {
return null;
}
}
`;
str = PatcherUtils.insertAt(str, index, newCode); str = PatcherUtils.insertAt(str, index, newCode);
return str; return str;
}, },
@ -903,8 +909,8 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
// Disable long touch activating context menu // Disable long touch activating context menu
disableTouchContextMenu(str: string) { disableTouchContextMenu(str: string) {
let index = str.indexOf('"ContextualCardActions-module__container'); let index = str.indexOf('.addEventListener("touchstart",');
index >= 0 && (index = str.indexOf('addEventListener("touchstart"', index)); index >= 0 && (index = PatcherUtils.indexOf(str, '.addEventListener("touchend"', index, 200));
index >= 0 && (index = PatcherUtils.lastIndexOf(str, 'return ', index, 50)); index >= 0 && (index = PatcherUtils.lastIndexOf(str, 'return ', index, 50));
if (index < 0) { if (index < 0) {
return false; return false;
@ -936,6 +942,10 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
return PatcherUtils.patchBeforePageLoad(str, 'stream'); return PatcherUtils.patchBeforePageLoad(str, 'stream');
}, },
remotePlayStreamPageBeforeLoad(str: string) {
return PatcherUtils.patchBeforePageLoad(str, 'remote-play-stream');
},
disableAbsoluteMouse(str: string) { disableAbsoluteMouse(str: string) {
let text = 'sendAbsoluteMouseCapableMessage(e){'; let text = 'sendAbsoluteMouseCapableMessage(e){';
if (!str.includes(text)) { if (!str.includes(text)) {
@ -1014,22 +1024,26 @@ ${subsVar} = subs;
} }
// Find function's parameter // Find function's parameter
const arrowIndex = PatcherUtils.lastIndexOf(str, '=>{', initialIndex, 300); const productIdIndex = PatcherUtils.lastIndexOf(str, ',productId:', initialIndex, 300);
if (arrowIndex < 0) { if (productIdIndex < 0) {
return false; return false;
} }
const paramVar = PatcherUtils.getVariableNameBefore(str, arrowIndex); const params = PatcherUtils.findAndParseParams(str, productIdIndex - 200, 400);
if (!params || !params.productId) {
return false;
}
const productIdVar = params.productId;
// Find supportedInputIcons and title var names // Find supportedInputIcons and title var names
const supportedInputIconsVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'supportedInputIcons:', initialIndex, 100, true)); const supportedInputIconsVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'supportedInputIcons:', initialIndex, 100, true));
if (!supportedInputIconsVar) {
if (!paramVar || !supportedInputIconsVar) {
return false; return false;
} }
const newCode = renderString(codeGameCardIcons, { const newCode = renderString(codeGameCardIcons, {
param: paramVar, productId: productIdVar,
supportedInputIcons: supportedInputIconsVar, supportedInputIcons: supportedInputIconsVar,
}); });
@ -1099,7 +1113,7 @@ ${subsVar} = subs;
}, },
injectHeaderUseEffect(str: string) { injectHeaderUseEffect(str: string) {
let index = str.indexOf('"EdgewaterHeader-module__spaceBetween'); let index = str.indexOf('className:"Header-module__header');
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 300)); index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 300));
if (index < 0) { if (index < 0) {
return false; return false;
@ -1110,12 +1124,15 @@ ${subsVar} = subs;
injectErrorPageUseEffect(str: string) { injectErrorPageUseEffect(str: string) {
let index = str.indexOf('"PureErrorPage-module__container'); let index = str.indexOf('"PureErrorPage-module__container');
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 200)); index > -1 && (index = PatcherUtils.lastIndexOf(str, '})=>(0,', index, 200));
if (index < 0) { if (index < 0) {
return false; return false;
} }
return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.error.rendered'); str = PatcherUtils.insertAt(str, index + 4, '{return ');
str = PatcherUtils.injectUseEffect(str, index + 5, 'Script', 'ui.error.rendered');
str += '}';
return str;
}, },
injectStreamMenuUseEffect(str: string) { injectStreamMenuUseEffect(str: string) {
@ -1169,6 +1186,36 @@ ${subsVar} = subs;
return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.guideAchievementDetail.rendered'); return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.guideAchievementDetail.rendered');
}, },
patchCustomInputIcon(str: string) {
let index = str.indexOf('.MouseAndKeyboard="MouseAndKeyboard"');
if (index < 0) {
return false;
}
// Get productId
const productIdMatch = /const (\w+)=(\w+)=>{/.exec(str.substring(index, index + 200));
if (!productIdMatch) {
return false;
}
// Define productId variable
str = str.replace(productIdMatch[0], productIdMatch[0] + `const productId = ${productIdMatch[2]};`);
let match = /(\w+)&&(\w+\.push\(\w+\.Touch\))/.exec(str);
if (!match) {
return false;
}
str = str.replace(match[0], `(${match[1]} || window.BX_EXPOSED.hasCustomTouchControl(productId)) && ${match[2]}`);
match = /(\w+)&&(\w+\.push\(\w+\.MouseAndKeyboard\))/.exec(str);
if (match) {
str = str.replace(match[0], `(${match[1]} || window.BX_EXPOSED.hasCustomNativeMkb(productId)) && ${match[2]}`);
}
return str;
},
/* /*
patchBasicGameInfo(str: string) { patchBasicGameInfo(str: string) {
let index = str.indexOf('.ChildXboxTitleIds,offerings'); let index = str.indexOf('.ChildXboxTitleIds,offerings');
@ -1230,6 +1277,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
'injectErrorPageUseEffect', 'injectErrorPageUseEffect',
'streamPageBeforeLoad', 'streamPageBeforeLoad',
'remotePlayStreamPageBeforeLoad',
'injectGuideHomeUseEffect', 'injectGuideHomeUseEffect',
'injectAchievementsProgressUseEffect', 'injectAchievementsProgressUseEffect',
@ -1240,6 +1288,8 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
'homePageBeforeLoad', 'homePageBeforeLoad',
'patchCustomInputIcon',
'gameCardCustomIcons', 'gameCardCustomIcons',
// 'gameCardPassTitle', // 'gameCardPassTitle',
@ -1266,7 +1316,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
'disableTelemetryProvider', 'disableTelemetryProvider',
] : []) as PatchArray, ] : []) as PatchArray,
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [ ...(!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY) ? [
'remotePlayKeepAlive', 'remotePlayKeepAlive',
'remotePlayDisableAchievementToast', 'remotePlayDisableAchievementToast',
STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync', STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync',
@ -1329,16 +1379,13 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
(getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF && (getGlobalPref(GlobalPref.MKB_ENABLED) || getGlobalPref(GlobalPref.NATIVE_MKB_MODE) === NativeMkbMode.ON)) && 'patchBabylonRendererClass', (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF && (getGlobalPref(GlobalPref.MKB_ENABLED) || getGlobalPref(GlobalPref.NATIVE_MKB_MODE) === NativeMkbMode.ON)) && 'patchBabylonRendererClass',
] : []) as PatchArray, ] : []) as PatchArray,
BX_FLAGS.EnableXcloudLogging && 'enableConsoleLogging',
'patchPollGamepads', 'patchPollGamepads',
getGlobalPref(GlobalPref.STREAM_COMBINE_SOURCES) && 'streamCombineSources', getGlobalPref(GlobalPref.STREAM_COMBINE_SOURCES) && 'streamCombineSources',
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [ ...(!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY) ? [
'remotePlayPostStreamRedirectUrl', 'remotePlayPostStreamRedirectUrl',
'patchRemotePlayMkb', 'patchRemotePlayMkb',
'remotePlayConnectMode',
] : []) as PatchArray, ] : []) as PatchArray,
// Native MKB // Native MKB
@ -1358,6 +1405,7 @@ export class Patcher {
private static remainingPatches: { [key in PatchPage]: PatchArray } = { private static remainingPatches: { [key in PatchPage]: PatchArray } = {
home: HOME_PAGE_PATCH_ORDERS, home: HOME_PAGE_PATCH_ORDERS,
stream: STREAM_PAGE_PATCH_ORDERS, stream: STREAM_PAGE_PATCH_ORDERS,
'remote-play-stream': STREAM_PAGE_PATCH_ORDERS,
'product-detail': PRODUCT_DETAIL_PAGE_PATCH_ORDERS, 'product-detail': PRODUCT_DETAIL_PAGE_PATCH_ORDERS,
}; };
@ -1521,7 +1569,9 @@ export class PatcherCache {
BxLogger.info(LOG_TAG, 'Cache', this.CACHE); BxLogger.info(LOG_TAG, 'Cache', this.CACHE);
const pathName = window.location.pathname; const pathName = window.location.pathname;
if (pathName.includes('/play/launch/')) { if (pathName.includes('/play/consoles/launch/')) {
Patcher.patchPage('remote-play-stream');
} else if (pathName.includes('/play/launch/')) {
Patcher.patchPage('stream'); Patcher.patchPage('stream');
} else if (pathName.includes('/play/games/')) { } else if (pathName.includes('/play/games/')) {
Patcher.patchPage('product-detail'); Patcher.patchPage('product-detail');
@ -1540,24 +1590,25 @@ export class PatcherCache {
/** /**
* Get patch's signature * Get patch's signature
*/ */
private getSignature(): number { private getSignature(): string {
const scriptVersion = SCRIPT_VERSION; const scriptVersion = SCRIPT_VERSION;
const patches = JSON.stringify(ALL_PATCHES); const patches = JSON.stringify(ALL_PATCHES);
// Get client.js's hash // Get client.js's hash
let webVersion = ''; let clientHash = '';
const $link = document.querySelector<HTMLLinkElement>('link[data-chunk="client"][href*="/client."]'); const $link = document.querySelector<HTMLLinkElement>('link[data-chunk="client"][as="script"][href*="/client."]');
if ($link) { if ($link) {
const match = /\/client\.([^\.]+)\.js/.exec($link.href); const match = /\/client\.([^\.]+)\.js/.exec($link.href);
match && (webVersion = match[1]); match && (clientHash = match[1]);
} else {
// Get version from <meta>
// Sometimes this value is missing
webVersion = (document.querySelector<HTMLMetaElement>('meta[name=gamepass-app-version]'))?.content ?? '';
} }
// Get version from <meta>
// Sometimes this value is missing
const webVersion = (document.querySelector<HTMLMetaElement>('meta[name=gamepass-app-version]'))?.content ?? '';
const webVersionDate = (document.querySelector<HTMLMetaElement>('meta[name=gamepass-app-date]'))?.content ?? '';
// Calculate signature // Calculate signature
const sig = hashCode(scriptVersion + webVersion + patches) const sig = `${scriptVersion}:${clientHash}:${webVersion}:${webVersionDate}:${hashCode(patches)}`;
return sig; return sig;
} }
@ -1571,7 +1622,7 @@ export class PatcherCache {
const storedSig = window.localStorage.getItem(this.KEY_SIGNATURE) || 0; const storedSig = window.localStorage.getItem(this.KEY_SIGNATURE) || 0;
const currentSig = this.getSignature(); const currentSig = this.getSignature();
if (currentSig !== parseInt(storedSig as string)) { if (currentSig !== storedSig) {
// Save new signature // Save new signature
BxLogger.warning(LOG_TAG, 'Signature changed'); BxLogger.warning(LOG_TAG, 'Signature changed');
window.localStorage.setItem(this.KEY_SIGNATURE, currentSig.toString()); window.localStorage.setItem(this.KEY_SIGNATURE, currentSig.toString());

View File

@ -1,8 +1,8 @@
declare const $supportedInputIcons$: Array<any>; declare const $supportedInputIcons$: Array<any>;
declare const $param$: { productId: string }; declare const $productId$: string;
const supportedInputIcons = $supportedInputIcons$; const supportedInputIcons = $supportedInputIcons$;
const { productId } = $param$; const productId = $productId$;
// Remove controller icon // Remove controller icon
supportedInputIcons.shift(); supportedInputIcons.shift();

View File

@ -3,7 +3,7 @@ declare const e: string;
try { try {
const msg = JSON.parse(e); const msg = JSON.parse(e);
if (msg.reason === 'WarningForBeingIdle' && window.location.pathname.includes('/consoles/launch/')) { if (msg.reason === 'WarningForBeingIdle' && window.location.pathname.includes('/play/consoles/launch/')) {
$this$.sendKeepAlive(); $this$.sendKeepAlive();
// @ts-ignore // @ts-ignore
return; return;

View File

@ -1,13 +1,13 @@
// @ts-ignore // @ts-ignore
declare const arguments: any; declare let $guideUI$: any;
declare const $onShowStreamMenu$: any;
const options = arguments[0]; declare const $offset$: any;
// Expose onShowStreamMenu // Expose onShowStreamMenu
window.BX_EXPOSED.showStreamMenu = options.onShowStreamMenu; window.BX_EXPOSED.showStreamMenu = $onShowStreamMenu$;
// Restore the "..." button // Restore the "..." button
options.guideUI = null; $guideUI$ = null;
window.BX_EXPOSED.reactUseEffect(() => { window.BX_EXPOSED.reactUseEffect(() => {
window.BxEventBus.Stream.emit('ui.streamHud.rendered', { expanded: options.offset.x === 0 }); window.BxEventBus.Stream.emit('ui.streamHud.rendered', { expanded: $offset$.x === 0 });
}); });

View File

@ -5,7 +5,7 @@ uniform sampler2D data;
uniform vec2 iResolution; uniform vec2 iResolution;
const int FILTER_UNSHARP_MASKING = 1; const int FILTER_UNSHARP_MASKING = 1;
// const int FILTER_CAS = 2; const int FILTER_CAS = 2;
// constrast = 0.8 // constrast = 0.8
const float CAS_CONTRAST_PEAK = 0.8 * -3.0 + 8.0; const float CAS_CONTRAST_PEAK = 0.8 * -3.0 + 8.0;
@ -14,6 +14,7 @@ const float CAS_CONTRAST_PEAK = 0.8 * -3.0 + 8.0;
const vec3 LUMINOSITY_FACTOR = vec3(0.299, 0.587, 0.114); const vec3 LUMINOSITY_FACTOR = vec3(0.299, 0.587, 0.114);
uniform int filterId; uniform int filterId;
uniform bool qualityMode;
uniform float sharpenFactor; uniform float sharpenFactor;
uniform float brightness; uniform float brightness;
uniform float contrast; uniform float contrast;
@ -28,16 +29,22 @@ vec3 clarityBoost(sampler2D tex, vec2 coord, vec3 e) {
// a b c // a b c
// d e f // d e f
// g h i // g h i
vec3 a = texture(tex, coord + texelSize * vec2(-1, 1)).rgb;
vec3 b = texture(tex, coord + texelSize * vec2(0, 1)).rgb; vec3 b = texture(tex, coord + texelSize * vec2(0, 1)).rgb;
vec3 c = texture(tex, coord + texelSize * vec2(1, 1)).rgb;
vec3 d = texture(tex, coord + texelSize * vec2(-1, 0)).rgb; vec3 d = texture(tex, coord + texelSize * vec2(-1, 0)).rgb;
vec3 f = texture(tex, coord + texelSize * vec2(1, 0)).rgb; vec3 f = texture(tex, coord + texelSize * vec2(1, 0)).rgb;
vec3 g = texture(tex, coord + texelSize * vec2(-1, -1)).rgb;
vec3 h = texture(tex, coord + texelSize * vec2(0, -1)).rgb; vec3 h = texture(tex, coord + texelSize * vec2(0, -1)).rgb;
vec3 i = texture(tex, coord + texelSize * vec2(1, -1)).rgb;
vec3 a;
vec3 c;
vec3 g;
vec3 i;
if (filterId == FILTER_UNSHARP_MASKING || qualityMode) {
a = texture(tex, coord + texelSize * vec2(-1, 1)).rgb;
c = texture(tex, coord + texelSize * vec2(1, 1)).rgb;
g = texture(tex, coord + texelSize * vec2(-1, -1)).rgb;
i = texture(tex, coord + texelSize * vec2(1, -1)).rgb;
}
// USM // USM
if (filterId == FILTER_UNSHARP_MASKING) { if (filterId == FILTER_UNSHARP_MASKING) {
@ -55,10 +62,12 @@ vec3 clarityBoost(sampler2D tex, vec2 coord, vec3 e) {
// g h i h // g h i h
// These are 2.0x bigger (factored out the extra multiply). // These are 2.0x bigger (factored out the extra multiply).
vec3 minRgb = min(min(min(d, e), min(f, b)), h); vec3 minRgb = min(min(min(d, e), min(f, b)), h);
minRgb += min(min(a, c), min(g, i));
vec3 maxRgb = max(max(max(d, e), max(f, b)), h); vec3 maxRgb = max(max(max(d, e), max(f, b)), h);
maxRgb += max(max(a, c), max(g, i));
if (qualityMode) {
minRgb += min(min(a, c), min(g, i));
maxRgb += max(max(a, c), max(g, i));
}
// Smooth minimum distance to signal limit divided by smooth max. // Smooth minimum distance to signal limit divided by smooth max.
vec3 reciprocalMaxRgb = 1.0 / maxRgb; vec3 reciprocalMaxRgb = 1.0 / maxRgb;
@ -85,10 +94,12 @@ void main() {
vec3 color = texture(data, uv).rgb; vec3 color = texture(data, uv).rgb;
// Clarity boost // Clarity boost
color = sharpenFactor > 0.0 ? clarityBoost(data, uv, color) : color; if (sharpenFactor > 0.0) {
color = clarityBoost(data, uv, color);
}
// Saturation // Saturation
color = saturation != 1.0 ? mix(vec3(dot(color, LUMINOSITY_FACTOR)), color, saturation) : color; color = mix(vec3(dot(color, LUMINOSITY_FACTOR)), color, saturation);
// Contrast // Contrast
color = contrast * (color - 0.5) + 0.5; color = contrast * (color - 0.5) + 0.5;

View File

@ -3,7 +3,7 @@ import { compressCodeFile } from "@macros/build" with { type: "macro" };
import { StreamPref } from "@/enums/pref-keys"; import { StreamPref } from "@/enums/pref-keys";
import { getStreamPref } from "@/utils/pref-utils"; import { getStreamPref } from "@/utils/pref-utils";
import { BaseCanvasPlayer } from "../base-canvas-player"; import { BaseCanvasPlayer } from "../base-canvas-player";
import { StreamPlayerType } from "@/enums/pref-values"; import { StreamPlayerType, StreamVideoProcessingMode } from "@/enums/pref-values";
export class WebGL2Player extends BaseCanvasPlayer { export class WebGL2Player extends BaseCanvasPlayer {
@ -25,7 +25,8 @@ export class WebGL2Player extends BaseCanvasPlayer {
gl.uniform2f(gl.getUniformLocation(program, 'iResolution'), this.$canvas.width, this.$canvas.height); gl.uniform2f(gl.getUniformLocation(program, 'iResolution'), this.$canvas.width, this.$canvas.height);
gl.uniform1i(gl.getUniformLocation(program, 'filterId'), filterId); gl.uniform1i(gl.getUniformLocation(program, 'filterId'), filterId);
gl.uniform1f(gl.getUniformLocation(program, 'sharpenFactor'), this.options.sharpness); gl.uniform1i(gl.getUniformLocation(program, 'qualityMode'), this.options.processingMode === StreamVideoProcessingMode.QUALITY ? 1 : 0);
gl.uniform1f(gl.getUniformLocation(program, 'sharpenFactor'), this.options.sharpness / (this.options.processingMode === StreamVideoProcessingMode.QUALITY ? 1 : 1.2));
gl.uniform1f(gl.getUniformLocation(program, 'brightness'), this.options.brightness / 100); gl.uniform1f(gl.getUniformLocation(program, 'brightness'), this.options.brightness / 100);
gl.uniform1f(gl.getUniformLocation(program, 'contrast'), this.options.contrast / 100); gl.uniform1f(gl.getUniformLocation(program, 'contrast'), this.options.contrast / 100);
gl.uniform1f(gl.getUniformLocation(program, 'saturation'), this.options.saturation / 100); gl.uniform1f(gl.getUniformLocation(program, 'saturation'), this.options.saturation / 100);

View File

@ -8,6 +8,7 @@ import { HeaderSection } from "./ui/header";
import { GlobalPref } from "@/enums/pref-keys"; import { GlobalPref } from "@/enums/pref-keys";
import { getGlobalPref, setGlobalPref } from "@/utils/pref-utils"; import { getGlobalPref, setGlobalPref } from "@/utils/pref-utils";
import { RemotePlayDialog } from "./ui/dialog/remote-play-dialog"; import { RemotePlayDialog } from "./ui/dialog/remote-play-dialog";
import { BlockFeature } from "@/enums/pref-values";
export const enum RemotePlayConsoleState { export const enum RemotePlayConsoleState {
ON = 'On', ON = 'On',
@ -37,7 +38,7 @@ export class RemotePlayManager {
private static instance: RemotePlayManager | null | undefined; private static instance: RemotePlayManager | null | undefined;
public static getInstance(): typeof RemotePlayManager['instance'] { public static getInstance(): typeof RemotePlayManager['instance'] {
if (typeof RemotePlayManager.instance === 'undefined') { if (typeof RemotePlayManager.instance === 'undefined') {
if (getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED)) { if (!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY)) {
RemotePlayManager.instance = new RemotePlayManager(); RemotePlayManager.instance = new RemotePlayManager();
} else { } else {
RemotePlayManager.instance = null; RemotePlayManager.instance = null;
@ -156,6 +157,11 @@ export class RemotePlayManager {
}, },
}; };
// Start with "isDefault" = true first
this.regions.sort((a: RemotePlayRegion, b: RemotePlayRegion) => {
return a.isDefault ? -1 : 0;
})
// Test servers one by one // Test servers one by one
for (const region of this.regions) { for (const region of this.regions) {
try { try {
@ -189,13 +195,7 @@ export class RemotePlayManager {
setGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, resolution, 'ui'); setGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, resolution, 'ui');
} }
STATES.remotePlay.config = { localRedirect('/consoles/launch/' + serverId);
serverId: serverId,
};
window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config;
localRedirect('/launch/fortnite/BT5P2X999VH2#remote-play');
setTimeout(() => localRedirect('/consoles/launch/' + serverId), 100);
} }
togglePopup(force = null) { togglePopup(force = null) {
@ -221,21 +221,6 @@ export class RemotePlayManager {
RemotePlayDialog.getInstance().show(); RemotePlayDialog.getInstance().show();
} }
static detect() {
if (!getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED)) {
return;
}
STATES.remotePlay.isPlaying = window.location.pathname.includes('/launch/') && window.location.hash.startsWith('#remote-play');
if (STATES.remotePlay?.isPlaying) {
window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config;
// Remove /launch/... from URL
window.history.replaceState({origin: 'better-xcloud'}, '', 'https://www.xbox.com/' + location.pathname.substring(1, 6) + '/play');
} else {
window.BX_REMOTE_PLAY_CONFIG = null;
}
}
isReady() { isReady() {
return this.consoles !== null; return this.consoles !== null;
} }

View File

@ -82,6 +82,10 @@ export class SettingsManager {
}, },
[StreamPref.VIDEO_PROCESSING]: { [StreamPref.VIDEO_PROCESSING]: {
onChange: updateVideoPlayer, onChange: updateVideoPlayer,
onChangeUi: onChangeVideoPlayerType,
},
[StreamPref.VIDEO_PROCESSING_MODE]: {
onChange: updateVideoPlayer,
}, },
[StreamPref.VIDEO_SHARPNESS]: { [StreamPref.VIDEO_SHARPNESS]: {
onChange: updateVideoPlayer, onChange: updateVideoPlayer,

View File

@ -8,6 +8,7 @@ import type { StreamPlayerOptions } from "@/types/stream";
export function onChangeVideoPlayerType() { export function onChangeVideoPlayerType() {
const playerType = getStreamPref(StreamPref.VIDEO_PLAYER_TYPE); const playerType = getStreamPref(StreamPref.VIDEO_PLAYER_TYPE);
const processing = getStreamPref(StreamPref.VIDEO_PROCESSING);
const settingsManager = SettingsManager.getInstance(); const settingsManager = SettingsManager.getInstance();
if (!settingsManager.hasElement(StreamPref.VIDEO_PROCESSING)) { if (!settingsManager.hasElement(StreamPref.VIDEO_PROCESSING)) {
return; return;
@ -16,6 +17,7 @@ export function onChangeVideoPlayerType() {
let isDisabled = false; let isDisabled = false;
const $videoProcessing = settingsManager.getElement(StreamPref.VIDEO_PROCESSING) as HTMLSelectElement; const $videoProcessing = settingsManager.getElement(StreamPref.VIDEO_PROCESSING) as HTMLSelectElement;
const $videoProcessingMode = settingsManager.getElement(StreamPref.VIDEO_PROCESSING_MODE) as HTMLSelectElement;
const $videoSharpness = settingsManager.getElement(StreamPref.VIDEO_SHARPNESS); const $videoSharpness = settingsManager.getElement(StreamPref.VIDEO_SHARPNESS);
const $videoPowerPreference = settingsManager.getElement(StreamPref.VIDEO_POWER_PREFERENCE); const $videoPowerPreference = settingsManager.getElement(StreamPref.VIDEO_POWER_PREFERENCE);
const $videoMaxFps = settingsManager.getElement(StreamPref.VIDEO_MAX_FPS); const $videoMaxFps = settingsManager.getElement(StreamPref.VIDEO_MAX_FPS);
@ -40,6 +42,7 @@ export function onChangeVideoPlayerType() {
$videoSharpness.dataset.disabled = isDisabled.toString(); $videoSharpness.dataset.disabled = isDisabled.toString();
// Hide Power Preference setting if renderer isn't WebGL2 // Hide Power Preference setting if renderer isn't WebGL2
$videoProcessingMode.closest('.bx-settings-row')!.classList.toggle('bx-gone', !(playerType === StreamPlayerType.WEBGL2 && processing === StreamVideoProcessing.CAS));
$videoPowerPreference.closest('.bx-settings-row')!.classList.toggle('bx-gone', playerType !== StreamPlayerType.WEBGL2); $videoPowerPreference.closest('.bx-settings-row')!.classList.toggle('bx-gone', playerType !== StreamPlayerType.WEBGL2);
$videoMaxFps.closest('.bx-settings-row')!.classList.toggle('bx-gone', playerType === StreamPlayerType.VIDEO); $videoMaxFps.closest('.bx-settings-row')!.classList.toggle('bx-gone', playerType === StreamPlayerType.VIDEO);
} }
@ -59,6 +62,7 @@ export function updateVideoPlayer() {
const options = { const options = {
processing: getStreamPref(StreamPref.VIDEO_PROCESSING), processing: getStreamPref(StreamPref.VIDEO_PROCESSING),
processingMode: getStreamPref(StreamPref.VIDEO_PROCESSING_MODE),
sharpness: getStreamPref(StreamPref.VIDEO_SHARPNESS), sharpness: getStreamPref(StreamPref.VIDEO_SHARPNESS),
saturation: getStreamPref(StreamPref.VIDEO_SATURATION), saturation: getStreamPref(StreamPref.VIDEO_SATURATION),
contrast: getStreamPref(StreamPref.VIDEO_CONTRAST), contrast: getStreamPref(StreamPref.VIDEO_CONTRAST),

View File

@ -267,6 +267,10 @@ export class TouchController {
return TouchController.#customList; return TouchController.#customList;
} }
static hasCustomControl(productId: string): boolean {
return TouchController.#customList?.includes(productId);
}
static setup() { static setup() {
// Function for testing touch control // Function for testing touch control
window.testTouchLayout = (layout: any) => { window.testTouchLayout = (layout: any) => {

View File

@ -51,7 +51,7 @@ export class RemotePlayDialog extends NavigationDialog {
$resolutions.addEventListener('input', (e: Event) => { $resolutions.addEventListener('input', (e: Event) => {
const value = (e.target as HTMLSelectElement).value; const value = (e.target as HTMLSelectElement).value;
$settingNote.textContent = value === StreamResolution.DIM_1080P ? '' + t('can-stream-xbox-360-games') : '❌ ' + t('cant-stream-xbox-360-games'); $settingNote.textContent = `${t('xbox-360-games')} ${value === StreamResolution.DIM_1080P_HQ ? '' : '✅'} ${t('xbox-apps')}`;
setGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, value, 'ui'); setGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, value, 'ui');
}); });

View File

@ -191,7 +191,6 @@ export class SettingsDialog extends NavigationDialog {
}, },
GlobalPref.SERVER_BYPASS_RESTRICTION, GlobalPref.SERVER_BYPASS_RESTRICTION,
GlobalPref.UI_CONTROLLER_FRIENDLY, GlobalPref.UI_CONTROLLER_FRIENDLY,
GlobalPref.REMOTE_PLAY_ENABLED,
], ],
}, { }, {
group: 'server', group: 'server',
@ -453,6 +452,7 @@ export class SettingsDialog extends NavigationDialog {
StreamPref.VIDEO_MAX_FPS, StreamPref.VIDEO_MAX_FPS,
StreamPref.VIDEO_POWER_PREFERENCE, StreamPref.VIDEO_POWER_PREFERENCE,
StreamPref.VIDEO_PROCESSING, StreamPref.VIDEO_PROCESSING,
StreamPref.VIDEO_PROCESSING_MODE,
StreamPref.VIDEO_RATIO, StreamPref.VIDEO_RATIO,
StreamPref.VIDEO_POSITION, StreamPref.VIDEO_POSITION,
StreamPref.VIDEO_SHARPNESS, StreamPref.VIDEO_SHARPNESS,

View File

@ -11,6 +11,7 @@ import { GlobalPref } from "@/enums/pref-keys";
import { getGlobalPref } from "@/utils/pref-utils"; import { getGlobalPref } from "@/utils/pref-utils";
import { BxLogger } from "@/utils/bx-logger"; import { BxLogger } from "@/utils/bx-logger";
import { BxEventBus } from "@/utils/bx-event-bus"; import { BxEventBus } from "@/utils/bx-event-bus";
import { BlockFeature } from "@/enums/pref-values";
export class HeaderSection { export class HeaderSection {
private static instance: HeaderSection; private static instance: HeaderSection;
@ -44,7 +45,7 @@ export class HeaderSection {
}); });
this.$buttonsWrapper = CE('div', false, this.$buttonsWrapper = CE('div', false,
getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? this.$btnRemotePlay : null, !getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY) ? this.$btnRemotePlay : null,
this.$btnSettings, this.$btnSettings,
); );
@ -74,7 +75,12 @@ export class HeaderSection {
} }
checkHeader = () => { checkHeader = () => {
let $target = document.querySelector('#PageContent div[class*=EdgewaterHeader-module__rightSectionSpacing]'); const $header = document.querySelector('#gamepass-root header[class^=Header-module__header]');
if (!$header) {
return;
}
let $target = $header.querySelector('div[class*=EdgewaterHeader-module__rightSectionSpacing], div[class*=RemotePlayHeader-module__rightSectionSpacing]');
if (!$target) { if (!$target) {
$target = document.querySelector('div[class^=UnsupportedMarketPage-module__buttons]'); $target = document.querySelector('div[class^=UnsupportedMarketPage-module__buttons]');
} }

View File

@ -26,7 +26,6 @@ declare global {
touchLayoutManager: any; touchLayoutManager: any;
}>; }>;
BX_REMOTE_PLAY_CONFIG: BxStates.remotePlay.config;
BX_STREAM_SETTINGS: StreamSettingsData; BX_STREAM_SETTINGS: StreamSettingsData;
BX_FETCH: typeof window['fetch']; BX_FETCH: typeof window['fetch'];

View File

@ -135,7 +135,7 @@ type XboxAchievement = {
} }
}; };
type OsName = 'windows' | 'tizen' | 'android'; type OsName = 'windows' | 'tizen' | 'webOS' | 'xboxOS' | 'android';
type XcloudGamepad = { type XcloudGamepad = {
GamepadIndex: number; GamepadIndex: number;

View File

@ -41,11 +41,7 @@ type BxStates = {
}>; }>;
remotePlay: Partial<{ remotePlay: Partial<{
isPlaying: boolean;
server: string; server: string;
config: {
serverId: string;
};
titleId?: string; titleId?: string;
}>; }>;

View File

@ -1,7 +1,8 @@
import type { StreamVideoProcessing } from "@/enums/pref-values"; import type { StreamVideoProcessing, StreamVideoProcessingMode } from "@/enums/pref-values";
type StreamPlayerOptions = { type StreamPlayerOptions = {
processing: StreamVideoProcessing, processing: StreamVideoProcessing,
processingMode: StreamVideoProcessingMode,
sharpness: number, sharpness: number,
saturation: number, saturation: number,
contrast: number, contrast: number,

View File

@ -261,4 +261,9 @@ export const BxExposed = {
), ),
); );
} : () => {}, } : () => {},
hasCustomTouchControl: TouchController.hasCustomControl,
hasCustomNativeMkb: (productId: string) => {
return BX_FLAGS.ForceNativeMkbTitles?.includes(productId);
}
}; };

View File

@ -10,7 +10,8 @@ export let FeatureGates: { [key: string]: boolean } = {
ShowForcedUpdateScreen: false, ShowForcedUpdateScreen: false,
EnableTakControlResizing: true, // Experimenting EnableTakControlResizing: true, // Experimenting
EnableLazyLoadedHome: false, EnableLazyLoadedHome: false,
EnableRemotePlay: getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED), EnableRemotePlay: !getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY),
EnableConsoles: !getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY),
}; };
// Enable Native Mouse & Keyboard // Enable Native Mouse & Keyboard

View File

@ -1,8 +1,5 @@
import { isFullVersion } from "@macros/build" with { type: "macro" };
import { BxEvent } from "@utils/bx-event"; import { BxEvent } from "@utils/bx-event";
import { LoadingScreen } from "@modules/loading-screen"; import { LoadingScreen } from "@modules/loading-screen";
import { RemotePlayManager } from "@/modules/remote-play-manager";
import { BxEventBus } from "./bx-event-bus"; import { BxEventBus } from "./bx-event-bus";
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog"; import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
@ -26,10 +23,6 @@ export function onHistoryChanged(e: PopStateEvent) {
return; return;
} }
if (isFullVersion()) {
window.setTimeout(RemotePlayManager.detect, 10);
}
// Hide Navigation dialog // Hide Navigation dialog
NavigationDialogManager.getInstance().hide(); NavigationDialogManager.getInstance().hide();

View File

@ -22,6 +22,7 @@ export function patchVideoApi() {
const playerOptions = { const playerOptions = {
processing: getStreamPref(StreamPref.VIDEO_PROCESSING), processing: getStreamPref(StreamPref.VIDEO_PROCESSING),
processingMode: getStreamPref(StreamPref.VIDEO_PROCESSING_MODE),
sharpness: getStreamPref(StreamPref.VIDEO_SHARPNESS), sharpness: getStreamPref(StreamPref.VIDEO_SHARPNESS),
saturation: getStreamPref(StreamPref.VIDEO_SATURATION), saturation: getStreamPref(StreamPref.VIDEO_SATURATION),
contrast: getStreamPref(StreamPref.VIDEO_CONTRAST), contrast: getStreamPref(StreamPref.VIDEO_CONTRAST),

View File

@ -49,8 +49,11 @@ function updateIceCandidates(candidates: any, options: { preferIpv6Server: boole
continue; continue;
} }
const groups: { [index: string]: string | number } = pattern.exec(item.candidate)!.groups!; const match = pattern.exec(item.candidate);
lst.push(groups); if (match && match.groups) {
const groups: { [index: string]: string | number } = match.groups;
lst.push(groups);
}
} }
if (options.preferIpv6Server) { if (options.preferIpv6Server) {
@ -294,7 +297,7 @@ export function interceptHttpRequests() {
} }
let requestType: RequestType; let requestType: RequestType;
if (url.includes('/sessions/home') || url.includes('xhome.') || (STATES.remotePlay.isPlaying && url.endsWith('/inputconfigs'))) { if (url.includes('/sessions/home') || url.includes('xhome.') || (window.location.pathname.includes('/play/consoles/launch/') && url.endsWith('/inputconfigs'))) {
requestType = 'xhome'; requestType = 'xhome';
} else { } else {
requestType = 'xcloud'; requestType = 'xcloud';

View File

@ -11,7 +11,6 @@ import { BaseSettingsStorage } from "./base-settings-storage";
import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerStyleStandard, TouchControllerStyleCustom, GameBarPosition, NativeMkbMode, UiLayout, UiSection, BlockFeature, UiTheme } from "@/enums/pref-values"; import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerStyleStandard, TouchControllerStyleCustom, GameBarPosition, NativeMkbMode, UiLayout, UiSection, BlockFeature, UiTheme } from "@/enums/pref-values";
import { GhPagesUtils } from "../gh-pages"; import { GhPagesUtils } from "../gh-pages";
import { BxEventBus } from "../bx-event-bus"; import { BxEventBus } from "../bx-event-bus";
import { BxIcon } from "../bx-icon";
function getSupportedCodecProfiles() { function getSupportedCodecProfiles() {
@ -497,6 +496,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage<GlobalPref> {
[BlockFeature.BYOG]: t('stream-your-own-game'), [BlockFeature.BYOG]: t('stream-your-own-game'),
[BlockFeature.NOTIFICATIONS_INVITES]: t('notifications') + ': ' + t('invites'), [BlockFeature.NOTIFICATIONS_INVITES]: t('notifications') + ': ' + t('invites'),
[BlockFeature.NOTIFICATIONS_ACHIEVEMENTS]: t('notifications') + ': ' + t('achievements'), [BlockFeature.NOTIFICATIONS_ACHIEVEMENTS]: t('notifications') + ': ' + t('achievements'),
[BlockFeature.REMOTE_PLAY]: t('remote-play'),
}, },
}, },
@ -525,13 +525,6 @@ export class GlobalSettingsStorage extends BaseSettingsStorage<GlobalPref> {
default: false, default: false,
}, },
[GlobalPref.REMOTE_PLAY_ENABLED]: {
requiredVariants: 'full',
label: t('enable-remote-play-feature'),
labelIcon: BxIcon.REMOTE_PLAY,
default: false,
},
[GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION]: { [GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION]: {
requiredVariants: 'full', requiredVariants: 'full',
default: StreamResolution.DIM_1080P, default: StreamResolution.DIM_1080P,

View File

@ -1,5 +1,5 @@
import { StreamPref, StorageKey, type PrefTypeMap } from "@/enums/pref-keys"; import { StreamPref, StorageKey, type PrefTypeMap } from "@/enums/pref-keys";
import { DeviceVibrationMode, StreamPlayerType, StreamVideoProcessing, VideoPowerPreference, VideoRatio, VideoPosition, StreamStat, StreamStatPosition } from "@/enums/pref-values"; import { DeviceVibrationMode, StreamPlayerType, StreamVideoProcessing, VideoPowerPreference, VideoRatio, VideoPosition, StreamStat, StreamStatPosition, StreamVideoProcessingMode } from "@/enums/pref-values";
import { STATES } from "../global"; import { STATES } from "../global";
import { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table"; import { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table";
import { MkbMappingDefaultPresetId } from "../local-db/mkb-mapping-presets-table"; import { MkbMappingDefaultPresetId } from "../local-db/mkb-mapping-presets-table";
@ -179,6 +179,18 @@ export class StreamSettingsStorage extends BaseSettingsStorage<StreamPref> {
highest: StreamVideoProcessing.CAS, highest: StreamVideoProcessing.CAS,
}, },
}, },
[StreamPref.VIDEO_PROCESSING_MODE]: {
label: t('clarity-boost-mode'),
default: StreamVideoProcessingMode.PERFORMANCE,
options: {
[StreamVideoProcessingMode.PERFORMANCE]: t('performance'),
[StreamVideoProcessingMode.QUALITY]: t('quality'),
},
suggest: {
lowest: StreamVideoProcessingMode.PERFORMANCE,
highest: StreamVideoProcessingMode.QUALITY,
},
},
[StreamPref.VIDEO_POWER_PREFERENCE]: { [StreamPref.VIDEO_POWER_PREFERENCE]: {
label: t('renderer-configuration'), label: t('renderer-configuration'),
default: VideoPowerPreference.DEFAULT, default: VideoPowerPreference.DEFAULT,
@ -228,10 +240,13 @@ export class StreamSettingsStorage extends BaseSettingsStorage<StreamPref> {
default: VideoRatio['16:9'], default: VideoRatio['16:9'],
options: { options: {
[VideoRatio['16:9']]: `16:9 (${t('default')})`, [VideoRatio['16:9']]: `16:9 (${t('default')})`,
[VideoRatio['18:9']]: '18:9',
[VideoRatio['21:9']]: '21:9',
[VideoRatio['16:10']]: '16:10', [VideoRatio['16:10']]: '16:10',
[VideoRatio['18:9']]: '18:9',
[VideoRatio['20:9']]: '20:9',
[VideoRatio['21:9']]: '21:9',
[VideoRatio['3:2']]: '3:2',
[VideoRatio['4:3']]: '4:3', [VideoRatio['4:3']]: '4:3',
[VideoRatio['5:4']]: '5:4',
[VideoRatio.FILL]: t('stretch'), [VideoRatio.FILL]: t('stretch'),
//'cover': 'Cover', //'cover': 'Cover',

View File

@ -7,6 +7,7 @@ export const SUPPORTED_LANGUAGES = {
'en-US': 'English (US)', 'en-US': 'English (US)',
'ca-CA': 'Català', 'ca-CA': 'Català',
'cs-CZ': 'čeština',
'da-DK': 'dansk', 'da-DK': 'dansk',
'de-DE': 'Deutsch', 'de-DE': 'Deutsch',
'en-ID': 'Bahasa Indonesia', 'en-ID': 'Bahasa Indonesia',
@ -60,12 +61,11 @@ const Texts = {
"browser-unsupported-feature": "Your browser doesn't support this feature", "browser-unsupported-feature": "Your browser doesn't support this feature",
"button-xbox": "Xbox button", "button-xbox": "Xbox button",
"bypass-region-restriction": "Bypass region restriction", "bypass-region-restriction": "Bypass region restriction",
"can-stream-xbox-360-games": "Can stream Xbox 360 games",
"cancel": "Cancel", "cancel": "Cancel",
"cant-stream-xbox-360-games": "Can't stream Xbox 360 games",
"center": "Center", "center": "Center",
"chat": "Chat", "chat": "Chat",
"clarity-boost": "Clarity boost", "clarity-boost": "Clarity boost",
"clarity-boost-mode": "Clarity boost mode",
"clarity-boost-warning": "These settings don't work when the Clarity Boost mode is ON", "clarity-boost-warning": "These settings don't work when the Clarity Boost mode is ON",
"clear": "Clear", "clear": "Clear",
"clear-data": "Clear data", "clear-data": "Clear data",
@ -198,6 +198,7 @@ const Texts = {
"new-version-available": [ "new-version-available": [
(e: any) => `Version ${e.version} available`, (e: any) => `Version ${e.version} available`,
(e: any) => `Versió ${e.version} disponible`, (e: any) => `Versió ${e.version} disponible`,
(e: any) => `Verze ${e.version} dostupná`,
, ,
(e: any) => `Version ${e.version} verfügbar`, (e: any) => `Version ${e.version} verfügbar`,
(e: any) => `Versi ${e.version} tersedia`, (e: any) => `Versi ${e.version} tersedia`,
@ -227,6 +228,7 @@ const Texts = {
"only-supports-some-games": "Only supports some games", "only-supports-some-games": "Only supports some games",
"opacity": "Opacity", "opacity": "Opacity",
"other": "Other", "other": "Other",
"performance": "Performance",
"playing": "Playing", "playing": "Playing",
"playtime": "Playtime", "playtime": "Playtime",
"poland": "Poland", "poland": "Poland",
@ -243,6 +245,7 @@ const Texts = {
"press-key-to-toggle-mkb": [ "press-key-to-toggle-mkb": [
(e: any) => `Press ${e.key} to toggle this feature`, (e: any) => `Press ${e.key} to toggle this feature`,
(e: any) => `Premeu ${e.key} per alternar aquesta funció`, (e: any) => `Premeu ${e.key} per alternar aquesta funció`,
(e: any) => `Zmáčknete ${e.key} pro přepnutí této funkce`,
(e: any) => `Tryk på ${e.key} for at slå denne funktion til`, (e: any) => `Tryk på ${e.key} for at slå denne funktion til`,
(e: any) => `${e.key}: Funktion an-/ausschalten`, (e: any) => `${e.key}: Funktion an-/ausschalten`,
(e: any) => `Tekan ${e.key} untuk mengaktifkan fitur ini`, (e: any) => `Tekan ${e.key} untuk mengaktifkan fitur ini`,
@ -263,11 +266,13 @@ const Texts = {
], ],
"press-to-bind": "Press a key or do a mouse click to bind...", "press-to-bind": "Press a key or do a mouse click to bind...",
"prompt-preset-name": "Preset's name:", "prompt-preset-name": "Preset's name:",
"quality": "Quality",
"recommended": "Recommended", "recommended": "Recommended",
"recommended-settings-for-device": [ "recommended-settings-for-device": [
(e: any) => `Recommended settings for ${e.device}`, (e: any) => `Recommended settings for ${e.device}`,
(e: any) => `Configuració recomanada per a ${e.device}`, (e: any) => `Configuració recomanada per a ${e.device}`,
, ,
,
(e: any) => `Empfohlene Einstellungen für ${e.device}`, (e: any) => `Empfohlene Einstellungen für ${e.device}`,
(e: any) => `Rekomendasi pengaturan untuk ${e.device}`, (e: any) => `Rekomendasi pengaturan untuk ${e.device}`,
(e: any) => `Ajustes recomendados para ${e.device}`, (e: any) => `Ajustes recomendados para ${e.device}`,
@ -317,6 +322,7 @@ const Texts = {
"separate-touch-controller": "Separate Touch controller & Controller #1", "separate-touch-controller": "Separate Touch controller & Controller #1",
"separate-touch-controller-note": "Touch controller is Player 1, Controller #1 is Player 2", "separate-touch-controller-note": "Touch controller is Player 1, Controller #1 is Player 2",
"server": "Server", "server": "Server",
"server-list-error": "Can't get the server list",
"server-locations": "Server locations", "server-locations": "Server locations",
"settings": "Settings", "settings": "Settings",
"settings-for": "Settings for", "settings-for": "Settings for",
@ -366,6 +372,7 @@ const Texts = {
"tc-custom-layout-style": "Custom layout's button style", "tc-custom-layout-style": "Custom layout's button style",
"tc-muted-colors": "Muted colors", "tc-muted-colors": "Muted colors",
"tc-standard-layout-style": "Standard layout's button style", "tc-standard-layout-style": "Standard layout's button style",
"test-controller": "Test controller",
"text-size": "Text size", "text-size": "Text size",
"theme": "Theme", "theme": "Theme",
"toggle": "Toggle", "toggle": "Toggle",
@ -378,6 +385,7 @@ const Texts = {
"touch-control-layout-by": [ "touch-control-layout-by": [
(e: any) => `Touch control layout by ${e.name}`, (e: any) => `Touch control layout by ${e.name}`,
(e: any) => `Format del control tàctil per ${e.name}`, (e: any) => `Format del control tàctil per ${e.name}`,
(e: any) => `Rozložení dotykového ovládání ${e.name}`,
(e: any) => `Touch-kontrol layout af ${e.name}`, (e: any) => `Touch-kontrol layout af ${e.name}`,
(e: any) => `Touch-Steuerungslayout von ${e.name}`, (e: any) => `Touch-Steuerungslayout von ${e.name}`,
(e: any) => `Tata letak Sentuhan layar oleh ${e.name}`, (e: any) => `Tata letak Sentuhan layar oleh ${e.name}`,
@ -430,6 +438,8 @@ const Texts = {
"wallpaper": "Wallpaper", "wallpaper": "Wallpaper",
"webgl2": "WebGL2", "webgl2": "WebGL2",
"webgpu": "WebGPU", "webgpu": "WebGPU",
"xbox-360-games": "Xbox 360 games",
"xbox-apps": "Xbox apps",
}; };
export class Translations { export class Translations {

View File

@ -16,6 +16,15 @@ export function checkForUpdate() {
return; return;
} }
// Always check for new version
fetch('https://api.github.com/repos/redphx/better-xcloud/releases/latest')
.then(response => response.json())
.then(json => {
// Store the latest version
setGlobalPref(GlobalPref.VERSION_LATEST, json.tag_name.substring(1), 'direct');
setGlobalPref(GlobalPref.VERSION_CURRENT, SCRIPT_VERSION, 'direct');
});
const CHECK_INTERVAL_SECONDS = 2 * 3600; // check every 2 hours const CHECK_INTERVAL_SECONDS = 2 * 3600; // check every 2 hours
const currentVersion = getGlobalPref(GlobalPref.VERSION_CURRENT); const currentVersion = getGlobalPref(GlobalPref.VERSION_CURRENT);
@ -28,13 +37,6 @@ export function checkForUpdate() {
// Start checking // Start checking
setGlobalPref(GlobalPref.VERSION_LAST_CHECK, now, 'direct'); setGlobalPref(GlobalPref.VERSION_LAST_CHECK, now, 'direct');
fetch('https://api.github.com/repos/redphx/better-xcloud/releases/latest')
.then(response => response.json())
.then(json => {
// Store the latest version
setGlobalPref(GlobalPref.VERSION_LATEST, json.tag_name.substring(1), 'direct');
setGlobalPref(GlobalPref.VERSION_CURRENT, SCRIPT_VERSION, 'direct');
});
// Update translations // Update translations
Translations.updateTranslations(currentVersion === SCRIPT_VERSION); Translations.updateTranslations(currentVersion === SCRIPT_VERSION);