mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-29 02:41:44 +02:00
Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
153b9e0cfe | |||
88151c9426 | |||
f7048a38b3 | |||
c76a3cc7a4 | |||
80f47a93d4 | |||
67fdf5ea12 | |||
4da2cfaf49 | |||
e1d053a634 | |||
f7f01fd27e | |||
67c2fb125f | |||
fad91d14a6 | |||
3a3fc77e83 | |||
4bf3bd3bb4 | |||
cc33e27bd6 | |||
9112853dfc | |||
368164b567 | |||
ce71c3043e | |||
3bb138cd05 | |||
3d6688e1db | |||
fc354e680c | |||
be51199279 | |||
e276d9a2b9 | |||
c2f9f129d0 | |||
aa50261726 | |||
bb32d97ae8 | |||
3d2abf6b12 | |||
4c8a49a43a | |||
256f28695e | |||
9e851fbd15 | |||
c829f74dcc | |||
62cf045f05 | |||
fdb4e58b5d | |||
b1407c2447 | |||
b5ba6e9600 | |||
a3094d2c9f | |||
3290a36886 | |||
e502e49d64 | |||
604cf7094a | |||
3bfa7e5f21 | |||
e3789b4fb7 | |||
0551d909e5 | |||
da6ab51ba0 | |||
4a65221ad0 |
25
.github/workflows/stale.yaml
vendored
Normal file
25
.github/workflows/stale.yaml
vendored
Normal 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'
|
62
bun.lock
62
bun.lock
@ -3,16 +3,16 @@
|
||||
"workspaces": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.6",
|
||||
"@types/node": "^22.13.13",
|
||||
"@types/stylus": "^0.48.43",
|
||||
"@webgpu/types": "^0.1.59",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-plugin-compat": "^6.0.2",
|
||||
"stylus": "^0.64.0",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "latest",
|
||||
"@types/stylus": "latest",
|
||||
"@webgpu/types": "latest",
|
||||
"eslint": "latest",
|
||||
"eslint-plugin-compat": "latest",
|
||||
"stylus": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -23,19 +23,19 @@
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
||||
|
||||
"@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/config-array": ["@eslint/config-array@0.20.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.2.0", "", {}, "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ=="],
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.2.1", "", {}, "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.12.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg=="],
|
||||
"@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="],
|
||||
|
||||
"@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/js": ["@eslint/js@9.24.0", "", {}, "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA=="],
|
||||
"@eslint/js": ["@eslint/js@9.29.0", "", {}, "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.7", "", { "dependencies": { "@eslint/core": "^0.12.0", "levn": "^0.4.1" } }, "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g=="],
|
||||
"@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=="],
|
||||
|
||||
@ -51,21 +51,19 @@
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.9", "", { "dependencies": { "bun-types": "1.2.9" } }, "sha512-epShhLGQYc4Bv/aceHbmBhOz1XgUnuTZgcxjxk+WXwNyDXavv5QHD1QEFV0FwbTSQtNq6g4ZcV6y0vZakTjswg=="],
|
||||
"@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
|
||||
|
||||
"@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/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="],
|
||||
"@types/node": ["@types/node@24.0.3", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg=="],
|
||||
|
||||
"@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.60", "", {}, "sha512-8B/tdfRFKdrnejqmvq95ogp8tf52oZ51p3f4QD5m5Paey/qlX4Rhhy5Y8tgFMi7Ms70HzcMMw3EQjH/jdhTwlA=="],
|
||||
|
||||
"acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
@ -85,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=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.9", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-dk/kOEfQbajENN/D6FyiSgOKEuUi9PWfqKQJEgwKrCMWbjS/S6tEXp178mWvWAcUSYm9ArDlWHZKO3T/4cLXiw=="],
|
||||
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
@ -115,15 +113,15 @@
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.24.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.0", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.24.0", "@eslint/plugin-kit": "^0.2.7", "@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-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ=="],
|
||||
"eslint": ["eslint@9.29.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.1", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.29.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.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.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-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ=="],
|
||||
|
||||
"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.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="],
|
||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="],
|
||||
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||
|
||||
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
||||
|
||||
@ -255,9 +253,9 @@
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"typescript": ["typescript@5.7.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg=="],
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
|
||||
"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,16 +273,18 @@
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@eslint/eslintrc/espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="],
|
||||
|
||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
|
||||
"@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/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=="],
|
||||
|
||||
"bun-types/@types/node": ["@types/node@22.15.32", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
@ -301,9 +301,13 @@
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@eslint/eslintrc/espree/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
|
||||
|
||||
"@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
||||
|
||||
"@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=="],
|
||||
"bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
|
||||
|
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 6.4.10
|
||||
// @version 6.7.0
|
||||
// ==/UserScript==
|
||||
|
757
dist/better-xcloud.pretty.user.js
vendored
757
dist/better-xcloud.pretty.user.js
vendored
File diff suppressed because one or more lines are too long
93
dist/better-xcloud.user.js
vendored
93
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
10
package.json
10
package.json
@ -10,15 +10,15 @@
|
||||
"build": "build.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.9",
|
||||
"@types/node": "^22.14.1",
|
||||
"@types/bun": "^1.2.17",
|
||||
"@types/node": "^24.0.3",
|
||||
"@types/stylus": "^0.48.43",
|
||||
"@webgpu/types": "^0.1.60",
|
||||
"eslint": "^9.24.0",
|
||||
"@webgpu/types": "^0.1.61",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-plugin-compat": "^6.0.2",
|
||||
"stylus": "^0.64.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.7.2"
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
@ -2,22 +2,29 @@
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #2d2d2d;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
}
|
||||
min-height: 30px;
|
||||
|
||||
label {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
> label {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
align-self: center;
|
||||
|
||||
p {
|
||||
margin: 4px 0 0;
|
||||
padding: 0;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
p {
|
||||
margin: 4px 0 0;
|
||||
padding: 0;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.bx-remote-play-resolution {
|
||||
@ -36,16 +43,16 @@
|
||||
.bx-remote-play-device-wrapper {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
gap: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-remote-play-device-info {
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.bx-remote-play-device-name {
|
||||
@ -73,7 +80,6 @@
|
||||
|
||||
.bx-remote-play-connect-button {
|
||||
min-height: 100%;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.bx-remote-play-buttons {
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 {
|
||||
GLOBAL = 'BetterXcloud',
|
||||
@ -79,8 +79,8 @@ export const enum GlobalPref {
|
||||
AUDIO_MIC_ON_PLAYING = 'audio.mic.onPlaying',
|
||||
AUDIO_VOLUME_CONTROL_ENABLED = 'audio.volume.booster.enabled',
|
||||
|
||||
REMOTE_PLAY_ENABLED = 'xhome.enabled',
|
||||
REMOTE_PLAY_STREAM_RESOLUTION = 'xhome.video.resolution',
|
||||
REMOTE_PLAY_PREFER_IPV6 = 'xhome.ipv6.prefer',
|
||||
|
||||
GAME_FORTNITE_FORCE_CONSOLE = 'game.fortnite.forceConsole',
|
||||
}
|
||||
@ -99,8 +99,8 @@ export type GlobalPrefTypeMap = {
|
||||
[GlobalPref.MKB_HIDE_IDLE_CURSOR]: boolean;
|
||||
[GlobalPref.NATIVE_MKB_FORCED_GAMES]: string[];
|
||||
[GlobalPref.NATIVE_MKB_MODE]: NativeMkbMode;
|
||||
[GlobalPref.REMOTE_PLAY_ENABLED]: boolean;
|
||||
[GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION]: StreamResolution;
|
||||
[GlobalPref.REMOTE_PLAY_PREFER_IPV6]: boolean;
|
||||
[GlobalPref.SCREENSHOT_APPLY_FILTERS]: boolean;
|
||||
[GlobalPref.SERVER_BYPASS_RESTRICTION]: string;
|
||||
[GlobalPref.SERVER_PREFER_IPV6]: boolean;
|
||||
@ -158,6 +158,7 @@ export const enum StreamPref {
|
||||
VIDEO_PLAYER_TYPE = 'video.player.type',
|
||||
VIDEO_POWER_PREFERENCE = 'video.player.powerPreference',
|
||||
VIDEO_PROCESSING = 'video.processing',
|
||||
VIDEO_PROCESSING_MODE = 'video.processing.mode',
|
||||
VIDEO_SHARPNESS = 'video.processing.sharpness',
|
||||
VIDEO_MAX_FPS = 'video.maxFps',
|
||||
VIDEO_RATIO = 'video.ratio',
|
||||
@ -207,6 +208,7 @@ export type StreamPrefTypeMap = {
|
||||
[StreamPref.VIDEO_POSITION]: VideoPosition;
|
||||
[StreamPref.VIDEO_POWER_PREFERENCE]: VideoPowerPreference;
|
||||
[StreamPref.VIDEO_PROCESSING]: StreamVideoProcessing;
|
||||
[StreamPref.VIDEO_PROCESSING_MODE]: StreamVideoProcessingMode;
|
||||
[StreamPref.VIDEO_RATIO]: VideoRatio;
|
||||
[StreamPref.VIDEO_SATURATION]: number;
|
||||
[StreamPref.VIDEO_SHARPNESS]: number;
|
||||
@ -232,8 +234,8 @@ export const ALL_PREFS: {
|
||||
GlobalPref.MKB_HIDE_IDLE_CURSOR,
|
||||
GlobalPref.NATIVE_MKB_FORCED_GAMES,
|
||||
GlobalPref.NATIVE_MKB_MODE,
|
||||
GlobalPref.REMOTE_PLAY_ENABLED,
|
||||
GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION,
|
||||
GlobalPref.REMOTE_PLAY_PREFER_IPV6,
|
||||
GlobalPref.SCREENSHOT_APPLY_FILTERS,
|
||||
GlobalPref.SERVER_BYPASS_RESTRICTION,
|
||||
GlobalPref.SERVER_PREFER_IPV6,
|
||||
@ -297,6 +299,7 @@ export const ALL_PREFS: {
|
||||
StreamPref.VIDEO_POSITION,
|
||||
StreamPref.VIDEO_POWER_PREFERENCE,
|
||||
StreamPref.VIDEO_PROCESSING,
|
||||
StreamPref.VIDEO_PROCESSING_MODE,
|
||||
StreamPref.VIDEO_RATIO,
|
||||
StreamPref.VIDEO_SATURATION,
|
||||
StreamPref.VIDEO_SHARPNESS,
|
||||
|
@ -95,10 +95,13 @@ export const enum StreamStatPosition {
|
||||
|
||||
export const enum VideoRatio {
|
||||
'16:9' = '16:9',
|
||||
'18:9' = '18:9',
|
||||
'21:9' = '21:9',
|
||||
'16:10' = '16:10',
|
||||
'18:9' = '18:9',
|
||||
'20:9' = '20:9',
|
||||
'21:9' = '21:9',
|
||||
'3:2' = '3:2',
|
||||
'4:3' = '4:3',
|
||||
'5:4' = '5:4',
|
||||
FILL = 'fill',
|
||||
}
|
||||
|
||||
@ -127,12 +130,18 @@ export const enum StreamVideoProcessing {
|
||||
CAS = 'cas',
|
||||
}
|
||||
|
||||
export const enum StreamVideoProcessingMode {
|
||||
QUALITY = 'quality',
|
||||
PERFORMANCE = 'performance',
|
||||
}
|
||||
|
||||
export const enum BlockFeature {
|
||||
CHAT = 'chat',
|
||||
FRIENDS = 'friends',
|
||||
BYOG = 'byog',
|
||||
NOTIFICATIONS_INVITES = 'notifications-invites',
|
||||
NOTIFICATIONS_ACHIEVEMENTS = 'notifications-achievements',
|
||||
REMOTE_PLAY = 'remote-play',
|
||||
}
|
||||
|
||||
export const enum UiTheme {
|
||||
|
22
src/index.ts
22
src/index.ts
@ -47,6 +47,8 @@ import { Toast } from "./utils/toast";
|
||||
import { WebGPUPlayer } from "./modules/player/webgpu/webgpu-player";
|
||||
import { StreamUiHandler } from "./modules/stream/stream-ui";
|
||||
import { TrueAchievements } from "./utils/true-achievements";
|
||||
import { localRedirect } from "./modules/ui/ui";
|
||||
import { handleDeepLink } from "./utils/deep-link";
|
||||
|
||||
SettingsManager.getInstance();
|
||||
|
||||
@ -190,7 +192,18 @@ document.addEventListener('readystatechange', e => {
|
||||
|
||||
// Preload fonts
|
||||
preloadFonts();
|
||||
})
|
||||
});
|
||||
|
||||
// Deep link
|
||||
if (AppInterface) {
|
||||
window.addEventListener(BxEvent.XCLOUD_ROUTER_HISTORY_READY, e => {
|
||||
if (window.location.pathname.includes('/fireos-browser-update')) {
|
||||
localRedirect('/play');
|
||||
} else {
|
||||
handleDeepLink();
|
||||
}
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
window.BX_EXPOSED = BxExposed;
|
||||
|
||||
@ -329,7 +342,7 @@ BxEventBus.Stream.on('dataChannelCreated', payload => {
|
||||
let newId: number = parseInt(json.titleid, 16);
|
||||
|
||||
// Get titleSlug for Remote Play
|
||||
if (STATES.remotePlay.isPlaying) {
|
||||
if (window.location.pathname.includes('/play/consoles/launch/')) {
|
||||
currentStream.titleSlug = 'remote-play';
|
||||
if (json.focused) {
|
||||
const productTitle = await XboxApi.getProductTitle(newId);
|
||||
@ -448,11 +461,6 @@ function main() {
|
||||
Patcher.init();
|
||||
disablePwa();
|
||||
|
||||
// Preload Remote Play
|
||||
if (getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED)) {
|
||||
RemotePlayManager.detect();
|
||||
}
|
||||
|
||||
if (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL) {
|
||||
TouchController.setup();
|
||||
}
|
||||
|
@ -36,18 +36,24 @@ export class PatcherUtils {
|
||||
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 {
|
||||
return patches.filter((item): item is PatchName => !!item);
|
||||
}
|
||||
|
||||
static patchBeforePageLoad(str: string, page: PatchPage): string | false {
|
||||
let text = `chunkName:()=>"${page}-page",`;
|
||||
if (!str.includes(text)) {
|
||||
const index = str.indexOf(`chunkName:()=>"${page}-page",`);
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
str = str.replace('requireAsync(e){', `requireAsync(e){window.BX_EXPOSED.beforePageLoad("${page}");`);
|
||||
str = str.replace('requireSync(e){', `requireSync(e){window.BX_EXPOSED.beforePageLoad("${page}");`);
|
||||
str = PatcherUtils.replaceAfterIndex(str, 'requireAsync(e){', `requireAsync(e){window.BX_EXPOSED.beforePageLoad("${page}");`, index);
|
||||
str = PatcherUtils.replaceAfterIndex(str, 'requireSync(e){', `requireSync(e){window.BX_EXPOSED.beforePageLoad("${page}");`, index);
|
||||
|
||||
return str;
|
||||
}
|
||||
@ -98,10 +104,47 @@ export class PatcherUtils {
|
||||
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) {
|
||||
const newCode = `window.BX_EXPOSED.reactUseEffect(() => window.BxEventBus.${group}.emit('${eventName}', {}), []);`;
|
||||
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}', {}), [])${separator}`;
|
||||
str = PatcherUtils.insertAt(str, index, newCode);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import { PatcherUtils } from "./patcher-utils.js";
|
||||
|
||||
export type PatchName = keyof typeof PATCHES;
|
||||
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;
|
||||
|
||||
const LOG_TAG = 'Patcher';
|
||||
@ -114,18 +114,6 @@ const PATCHES = {
|
||||
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
|
||||
remotePlayDisableAchievementToast(str: string) {
|
||||
let text = '.AchievementUnlock:{';
|
||||
@ -133,7 +121,7 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
|
||||
return false;
|
||||
}
|
||||
|
||||
const newCode = `if (window.location.pathname.includes('/consoles/launch/')) return;`;
|
||||
const newCode = `if (window.location.pathname.includes('/play/consoles/launch/')) return;`;
|
||||
return str.replace(text, text + newCode);
|
||||
},
|
||||
|
||||
@ -148,7 +136,7 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
|
||||
},
|
||||
|
||||
patchPollGamepads(str: string) {
|
||||
const index = str.indexOf('},this.pollGamepads=()=>{');
|
||||
const index = str.indexOf('()(this,"pollGamepads",');
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
@ -249,8 +237,12 @@ logFunc(logTag, '//', logMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
const constIndex = str.indexOf('const', index - 30);
|
||||
str = str.substring(0, constIndex) + 'e.onClose();return null;' + str.substring(constIndex);
|
||||
const constIndex = PatcherUtils.lastIndexOf(str, 'const[', index, 100);
|
||||
if (constIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
str = PatcherUtils.insertAt(str, constIndex, 'e();return null;');
|
||||
return str;
|
||||
},
|
||||
|
||||
@ -401,20 +393,40 @@ if (titleInfo && !titleInfo.details.hasTouchSupport && !titleInfo.details.hasFak
|
||||
},
|
||||
|
||||
patchStreamHud(str: string) {
|
||||
let index = str.indexOf('let{onCollapse');
|
||||
let index = str.indexOf('({onCollapse:');
|
||||
if (index < 0) {
|
||||
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
|
||||
if (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
|
||||
newCode += 'options.canShowTakHUD = false;';
|
||||
const canShowTakHUDVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'canShowTakHUD', index, 500, true) + 1);
|
||||
const guideUIVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'guideUI', index, 500, true) + 1);
|
||||
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) {
|
||||
@ -580,12 +592,13 @@ true` + text;
|
||||
},
|
||||
|
||||
skipFeedbackDialog(str: string) {
|
||||
let text = 'shouldTransitionToFeedback(e){';
|
||||
if (!str.includes(text)) {
|
||||
let index = str.indexOf('}shouldTransitionToFeedback(');
|
||||
index >= 0 && (index = PatcherUtils.indexOf(str, '}){', index, 200, true));
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
str = str.replace(text, text + 'return !1;');
|
||||
str = PatcherUtils.insertAt(str, index, 'return !1;');
|
||||
return str;
|
||||
},
|
||||
|
||||
@ -619,13 +632,12 @@ true` + text;
|
||||
},
|
||||
|
||||
exposeInputChannel(str: string) {
|
||||
let index = str.indexOf('this.flushData=');
|
||||
if (index < 0) {
|
||||
let text = '()(this,"flushData",(';
|
||||
if (!str.includes(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const newCode = 'window.BX_EXPOSED.inputChannel = this,';
|
||||
str = PatcherUtils.insertAt(str, index, newCode);
|
||||
str = str.replace(text, '()(window.BX_EXPOSED.inputChannel = this, "flushData", (');
|
||||
return str;
|
||||
},
|
||||
|
||||
@ -676,9 +688,9 @@ true` + text;
|
||||
|
||||
// Replace *qe*'s return value
|
||||
// `return a && r ?` => `return a && r || true ?`
|
||||
index = str.indexOf(`const ${funcName}=e=>{`);
|
||||
index > -1 && (index = str.indexOf('return ', index));
|
||||
index > -1 && (index = str.indexOf('?', index));
|
||||
index = str.indexOf(`const ${funcName}=({children`);
|
||||
index > -1 && (index = PatcherUtils.indexOf(str, 'return ', 300));
|
||||
index > -1 && (index = PatcherUtils.indexOf(str, '?', 100));
|
||||
|
||||
if (index < 0) {
|
||||
return false;
|
||||
@ -690,7 +702,7 @@ true` + text;
|
||||
|
||||
// Don't render News section
|
||||
ignoreNewsSection(str: string) {
|
||||
let index = str.indexOf('Logger("CarouselRow")');
|
||||
let index = str.indexOf('("CarouselRow"))');
|
||||
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'const ', index, 200));
|
||||
if (index < 0) {
|
||||
return false;
|
||||
@ -707,12 +719,12 @@ true` + text;
|
||||
return false;
|
||||
}
|
||||
|
||||
index = PatcherUtils.lastIndexOf(str, 'return', index, 50);
|
||||
index = PatcherUtils.lastIndexOf(str, '=>', index, 50);
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
str = PatcherUtils.replaceWith(str, index, 'return', 'return null;');
|
||||
str = PatcherUtils.replaceWith(str, index, '=>', '=> true ? null :');
|
||||
return str;
|
||||
},
|
||||
|
||||
@ -760,12 +772,13 @@ true` + text;
|
||||
// home-page.js
|
||||
ignoreSiglSections(str: string) {
|
||||
let index = str.indexOf('SiglRow-module__heroCard___');
|
||||
index >= 0 && (index = PatcherUtils.lastIndexOf(str, 'const[', index, 300));
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
index = PatcherUtils.lastIndexOf(str, 'const[', index, 300);
|
||||
if (index < 0) {
|
||||
const params = PatcherUtils.findAndParseParams(str, index - 500, 500);
|
||||
if (!params || !params.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -784,16 +797,9 @@ true` + text;
|
||||
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);
|
||||
return str;
|
||||
},
|
||||
@ -903,8 +909,8 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
|
||||
|
||||
// Disable long touch activating context menu
|
||||
disableTouchContextMenu(str: string) {
|
||||
let index = str.indexOf('arguments.length>2&&void 0!==arguments[2]?arguments[2]:500;');
|
||||
index >= 0 && (index = str.indexOf('addEventListener("touchstart"', index));
|
||||
let index = str.indexOf('.addEventListener("touchstart",');
|
||||
index >= 0 && (index = PatcherUtils.indexOf(str, '.addEventListener("touchend"', index, 200));
|
||||
index >= 0 && (index = PatcherUtils.lastIndexOf(str, 'return ', index, 50));
|
||||
if (index < 0) {
|
||||
return false;
|
||||
@ -936,6 +942,10 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
|
||||
return PatcherUtils.patchBeforePageLoad(str, 'stream');
|
||||
},
|
||||
|
||||
remotePlayStreamPageBeforeLoad(str: string) {
|
||||
return PatcherUtils.patchBeforePageLoad(str, 'remote-play-stream');
|
||||
},
|
||||
|
||||
disableAbsoluteMouse(str: string) {
|
||||
let text = 'sendAbsoluteMouseCapableMessage(e){';
|
||||
if (!str.includes(text)) {
|
||||
@ -1014,22 +1024,26 @@ ${subsVar} = subs;
|
||||
}
|
||||
|
||||
// Find function's parameter
|
||||
const arrowIndex = PatcherUtils.lastIndexOf(str, '=>{', initialIndex, 300);
|
||||
if (arrowIndex < 0) {
|
||||
const productIdIndex = PatcherUtils.lastIndexOf(str, ',productId:', initialIndex, 300);
|
||||
if (productIdIndex < 0) {
|
||||
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
|
||||
const supportedInputIconsVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'supportedInputIcons:', initialIndex, 100, true));
|
||||
|
||||
if (!paramVar || !supportedInputIconsVar) {
|
||||
if (!supportedInputIconsVar) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const newCode = renderString(codeGameCardIcons, {
|
||||
param: paramVar,
|
||||
productId: productIdVar,
|
||||
supportedInputIcons: supportedInputIconsVar,
|
||||
});
|
||||
|
||||
@ -1099,7 +1113,7 @@ ${subsVar} = subs;
|
||||
},
|
||||
|
||||
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));
|
||||
if (index < 0) {
|
||||
return false;
|
||||
@ -1110,12 +1124,15 @@ ${subsVar} = subs;
|
||||
|
||||
injectErrorPageUseEffect(str: string) {
|
||||
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) {
|
||||
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) {
|
||||
@ -1260,6 +1277,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||
'injectErrorPageUseEffect',
|
||||
|
||||
'streamPageBeforeLoad',
|
||||
'remotePlayStreamPageBeforeLoad',
|
||||
|
||||
'injectGuideHomeUseEffect',
|
||||
'injectAchievementsProgressUseEffect',
|
||||
@ -1298,7 +1316,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||
'disableTelemetryProvider',
|
||||
] : []) as PatchArray,
|
||||
|
||||
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [
|
||||
...(!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY) ? [
|
||||
'remotePlayKeepAlive',
|
||||
'remotePlayDisableAchievementToast',
|
||||
STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync',
|
||||
@ -1365,10 +1383,9 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||
|
||||
getGlobalPref(GlobalPref.STREAM_COMBINE_SOURCES) && 'streamCombineSources',
|
||||
|
||||
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [
|
||||
...(!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY) ? [
|
||||
'remotePlayPostStreamRedirectUrl',
|
||||
'patchRemotePlayMkb',
|
||||
'remotePlayConnectMode',
|
||||
] : []) as PatchArray,
|
||||
|
||||
// Native MKB
|
||||
@ -1388,6 +1405,7 @@ export class Patcher {
|
||||
private static remainingPatches: { [key in PatchPage]: PatchArray } = {
|
||||
home: HOME_PAGE_PATCH_ORDERS,
|
||||
stream: STREAM_PAGE_PATCH_ORDERS,
|
||||
'remote-play-stream': STREAM_PAGE_PATCH_ORDERS,
|
||||
'product-detail': PRODUCT_DETAIL_PAGE_PATCH_ORDERS,
|
||||
};
|
||||
|
||||
@ -1551,7 +1569,9 @@ export class PatcherCache {
|
||||
BxLogger.info(LOG_TAG, 'Cache', this.CACHE);
|
||||
|
||||
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');
|
||||
} else if (pathName.includes('/play/games/')) {
|
||||
Patcher.patchPage('product-detail');
|
||||
@ -1570,26 +1590,25 @@ export class PatcherCache {
|
||||
/**
|
||||
* Get patch's signature
|
||||
*/
|
||||
private getSignature(): number {
|
||||
private getSignature(): string {
|
||||
const scriptVersion = SCRIPT_VERSION;
|
||||
const patches = JSON.stringify(ALL_PATCHES);
|
||||
|
||||
// Get client.js's hash
|
||||
let webVersion = '';
|
||||
let clientHash = '';
|
||||
const $link = document.querySelector<HTMLLinkElement>('link[data-chunk="client"][as="script"][href*="/client."]');
|
||||
if ($link) {
|
||||
const match = /\/client\.([^\.]+)\.js/.exec($link.href);
|
||||
match && (webVersion = match[1]);
|
||||
match && (clientHash = match[1]);
|
||||
}
|
||||
|
||||
if (!webVersion) {
|
||||
// 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
|
||||
const sig = hashCode(scriptVersion + webVersion + patches)
|
||||
const sig = `${scriptVersion}:${clientHash}:${webVersion}:${webVersionDate}:${hashCode(patches)}`;
|
||||
return sig;
|
||||
}
|
||||
|
||||
@ -1603,7 +1622,7 @@ export class PatcherCache {
|
||||
const storedSig = window.localStorage.getItem(this.KEY_SIGNATURE) || 0;
|
||||
const currentSig = this.getSignature();
|
||||
|
||||
if (currentSig !== parseInt(storedSig as string)) {
|
||||
if (currentSig !== storedSig) {
|
||||
// Save new signature
|
||||
BxLogger.warning(LOG_TAG, 'Signature changed');
|
||||
window.localStorage.setItem(this.KEY_SIGNATURE, currentSig.toString());
|
||||
|
@ -1,8 +1,8 @@
|
||||
declare const $supportedInputIcons$: Array<any>;
|
||||
declare const $param$: { productId: string };
|
||||
declare const $productId$: string;
|
||||
|
||||
const supportedInputIcons = $supportedInputIcons$;
|
||||
const { productId } = $param$;
|
||||
const productId = $productId$;
|
||||
|
||||
// Remove controller icon
|
||||
supportedInputIcons.shift();
|
||||
|
@ -3,7 +3,7 @@ declare const e: string;
|
||||
|
||||
try {
|
||||
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();
|
||||
// @ts-ignore
|
||||
return;
|
||||
|
@ -1,13 +1,13 @@
|
||||
// @ts-ignore
|
||||
declare const arguments: any;
|
||||
|
||||
const options = arguments[0];
|
||||
declare let $guideUI$: any;
|
||||
declare const $onShowStreamMenu$: any;
|
||||
declare const $offset$: any;
|
||||
|
||||
// Expose onShowStreamMenu
|
||||
window.BX_EXPOSED.showStreamMenu = options.onShowStreamMenu;
|
||||
window.BX_EXPOSED.showStreamMenu = $onShowStreamMenu$;
|
||||
// Restore the "..." button
|
||||
options.guideUI = null;
|
||||
$guideUI$ = null;
|
||||
|
||||
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 });
|
||||
});
|
||||
|
@ -5,7 +5,7 @@ uniform sampler2D data;
|
||||
uniform vec2 iResolution;
|
||||
|
||||
const int FILTER_UNSHARP_MASKING = 1;
|
||||
// const int FILTER_CAS = 2;
|
||||
const int FILTER_CAS = 2;
|
||||
|
||||
// constrast = 0.8
|
||||
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);
|
||||
|
||||
uniform int filterId;
|
||||
uniform bool qualityMode;
|
||||
uniform float sharpenFactor;
|
||||
uniform float brightness;
|
||||
uniform float contrast;
|
||||
@ -28,16 +29,22 @@ vec3 clarityBoost(sampler2D tex, vec2 coord, vec3 e) {
|
||||
// a b c
|
||||
// d e f
|
||||
// g h i
|
||||
vec3 a = texture(tex, coord + texelSize * vec2(-1, 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 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 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
|
||||
if (filterId == FILTER_UNSHARP_MASKING) {
|
||||
@ -55,10 +62,12 @@ vec3 clarityBoost(sampler2D tex, vec2 coord, vec3 e) {
|
||||
// g h i h
|
||||
// These are 2.0x bigger (factored out the extra multiply).
|
||||
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);
|
||||
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.
|
||||
vec3 reciprocalMaxRgb = 1.0 / maxRgb;
|
||||
@ -85,10 +94,12 @@ void main() {
|
||||
vec3 color = texture(data, uv).rgb;
|
||||
|
||||
// Clarity boost
|
||||
color = sharpenFactor > 0.0 ? clarityBoost(data, uv, color) : color;
|
||||
if (sharpenFactor > 0.0) {
|
||||
color = clarityBoost(data, uv, color);
|
||||
}
|
||||
|
||||
// Saturation
|
||||
color = saturation != 1.0 ? mix(vec3(dot(color, LUMINOSITY_FACTOR)), color, saturation) : color;
|
||||
color = mix(vec3(dot(color, LUMINOSITY_FACTOR)), color, saturation);
|
||||
|
||||
// Contrast
|
||||
color = contrast * (color - 0.5) + 0.5;
|
||||
|
@ -3,7 +3,7 @@ import { compressCodeFile } from "@macros/build" with { type: "macro" };
|
||||
import { StreamPref } from "@/enums/pref-keys";
|
||||
import { getStreamPref } from "@/utils/pref-utils";
|
||||
import { BaseCanvasPlayer } from "../base-canvas-player";
|
||||
import { StreamPlayerType } from "@/enums/pref-values";
|
||||
import { StreamPlayerType, StreamVideoProcessingMode } from "@/enums/pref-values";
|
||||
|
||||
|
||||
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.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, 'contrast'), this.options.contrast / 100);
|
||||
gl.uniform1f(gl.getUniformLocation(program, 'saturation'), this.options.saturation / 100);
|
||||
|
@ -8,6 +8,7 @@ import { HeaderSection } from "./ui/header";
|
||||
import { GlobalPref } from "@/enums/pref-keys";
|
||||
import { getGlobalPref, setGlobalPref } from "@/utils/pref-utils";
|
||||
import { RemotePlayDialog } from "./ui/dialog/remote-play-dialog";
|
||||
import { BlockFeature } from "@/enums/pref-values";
|
||||
|
||||
export const enum RemotePlayConsoleState {
|
||||
ON = 'On',
|
||||
@ -37,7 +38,7 @@ export class RemotePlayManager {
|
||||
private static instance: RemotePlayManager | null | undefined;
|
||||
public static getInstance(): typeof RemotePlayManager['instance'] {
|
||||
if (typeof RemotePlayManager.instance === 'undefined') {
|
||||
if (getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED)) {
|
||||
if (!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY)) {
|
||||
RemotePlayManager.instance = new RemotePlayManager();
|
||||
} else {
|
||||
RemotePlayManager.instance = null;
|
||||
@ -194,13 +195,7 @@ export class RemotePlayManager {
|
||||
setGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, resolution, 'ui');
|
||||
}
|
||||
|
||||
STATES.remotePlay.config = {
|
||||
serverId: serverId,
|
||||
};
|
||||
window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config;
|
||||
|
||||
localRedirect('/launch/fortnite/BT5P2X999VH2#remote-play');
|
||||
setTimeout(() => localRedirect('/consoles/launch/' + serverId), 100);
|
||||
localRedirect('/consoles/launch/' + serverId);
|
||||
}
|
||||
|
||||
togglePopup(force = null) {
|
||||
@ -214,33 +209,9 @@ export class RemotePlayManager {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// Show native dialog in Android app
|
||||
if (AppInterface && AppInterface.showRemotePlayDialog) {
|
||||
AppInterface.showRemotePlayDialog(JSON.stringify(this.consoles));
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
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() {
|
||||
return this.consoles !== null;
|
||||
}
|
||||
|
@ -82,6 +82,10 @@ export class SettingsManager {
|
||||
},
|
||||
[StreamPref.VIDEO_PROCESSING]: {
|
||||
onChange: updateVideoPlayer,
|
||||
onChangeUi: onChangeVideoPlayerType,
|
||||
},
|
||||
[StreamPref.VIDEO_PROCESSING_MODE]: {
|
||||
onChange: updateVideoPlayer,
|
||||
},
|
||||
[StreamPref.VIDEO_SHARPNESS]: {
|
||||
onChange: updateVideoPlayer,
|
||||
|
@ -8,6 +8,7 @@ import type { StreamPlayerOptions } from "@/types/stream";
|
||||
|
||||
export function onChangeVideoPlayerType() {
|
||||
const playerType = getStreamPref(StreamPref.VIDEO_PLAYER_TYPE);
|
||||
const processing = getStreamPref(StreamPref.VIDEO_PROCESSING);
|
||||
const settingsManager = SettingsManager.getInstance();
|
||||
if (!settingsManager.hasElement(StreamPref.VIDEO_PROCESSING)) {
|
||||
return;
|
||||
@ -16,6 +17,7 @@ export function onChangeVideoPlayerType() {
|
||||
let isDisabled = false;
|
||||
|
||||
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 $videoPowerPreference = settingsManager.getElement(StreamPref.VIDEO_POWER_PREFERENCE);
|
||||
const $videoMaxFps = settingsManager.getElement(StreamPref.VIDEO_MAX_FPS);
|
||||
@ -40,6 +42,7 @@ export function onChangeVideoPlayerType() {
|
||||
$videoSharpness.dataset.disabled = isDisabled.toString();
|
||||
|
||||
// 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);
|
||||
$videoMaxFps.closest('.bx-settings-row')!.classList.toggle('bx-gone', playerType === StreamPlayerType.VIDEO);
|
||||
}
|
||||
@ -59,6 +62,7 @@ export function updateVideoPlayer() {
|
||||
|
||||
const options = {
|
||||
processing: getStreamPref(StreamPref.VIDEO_PROCESSING),
|
||||
processingMode: getStreamPref(StreamPref.VIDEO_PROCESSING_MODE),
|
||||
sharpness: getStreamPref(StreamPref.VIDEO_SHARPNESS),
|
||||
saturation: getStreamPref(StreamPref.VIDEO_SATURATION),
|
||||
contrast: getStreamPref(StreamPref.VIDEO_CONTRAST),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ButtonStyle, CE, createButton } from "@/utils/html";
|
||||
import { ButtonStyle, CE, createButton, escapeCssSelector } from "@/utils/html";
|
||||
import { NavigationDialog, type NavigationElement } from "./navigation-dialog";
|
||||
import { GlobalPref } from "@/enums/pref-keys";
|
||||
import { BxIcon } from "@/utils/bx-icon";
|
||||
@ -9,6 +9,9 @@ import { BxSelectElement } from "@/web-components/bx-select";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
import { BxLogger } from "@/utils/bx-logger";
|
||||
import { StreamResolution } from "@/enums/pref-values";
|
||||
import { setNearby } from "@/utils/navigation-utils";
|
||||
import { AppInterface } from "@/utils/global";
|
||||
import { SettingElement } from "@/utils/setting-element";
|
||||
|
||||
|
||||
export class RemotePlayDialog extends NavigationDialog {
|
||||
@ -51,7 +54,7 @@ export class RemotePlayDialog extends NavigationDialog {
|
||||
$resolutions.addEventListener('input', (e: Event) => {
|
||||
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');
|
||||
});
|
||||
|
||||
@ -65,6 +68,9 @@ export class RemotePlayDialog extends NavigationDialog {
|
||||
}, CE('div', false,
|
||||
CE('label', false, t('target-resolution'), $settingNote),
|
||||
$resolutions,
|
||||
), CE('div', false,
|
||||
CE('label', { 'for': `bx_setting_${escapeCssSelector(GlobalPref.REMOTE_PLAY_PREFER_IPV6)}` }, t('prefer-ipv6-server')),
|
||||
SettingElement.fromPref(GlobalPref.REMOTE_PLAY_PREFER_IPV6),
|
||||
));
|
||||
|
||||
$fragment.appendChild($qualitySettings);
|
||||
@ -73,8 +79,20 @@ export class RemotePlayDialog extends NavigationDialog {
|
||||
const manager = RemotePlayManager.getInstance()!;
|
||||
const consoles = manager.getConsoles();
|
||||
|
||||
const createConsoleShortcut = (e: Event) => {
|
||||
const { serverId, deviceName } = (e.target as HTMLElement).dataset;
|
||||
const optionsJson = JSON.stringify({
|
||||
'resolution': getGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION),
|
||||
});
|
||||
|
||||
AppInterface?.createConsoleShortcut(serverId!, deviceName!, optionsJson);
|
||||
};
|
||||
|
||||
for (let con of consoles) {
|
||||
const $child = CE('div', { class: 'bx-remote-play-device-wrapper' },
|
||||
let $connect;
|
||||
const $child = CE('div', {
|
||||
class: 'bx-remote-play-device-wrapper',
|
||||
},
|
||||
CE('div', { class: 'bx-remote-play-device-info' },
|
||||
CE('div', false,
|
||||
CE('span', { class: 'bx-remote-play-device-name' }, con.deviceName),
|
||||
@ -83,8 +101,20 @@ export class RemotePlayDialog extends NavigationDialog {
|
||||
CE('div', { class: 'bx-remote-play-power-state' }, this.STATE_LABELS[con.powerState]),
|
||||
),
|
||||
|
||||
// Shortcut button
|
||||
AppInterface ? createButton({
|
||||
attributes: {
|
||||
'data-server-id': con.serverId,
|
||||
'data-device-name': con.deviceName,
|
||||
},
|
||||
icon: BxIcon.CREATE_SHORTCUT,
|
||||
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE,
|
||||
title: t('create-shortcut'),
|
||||
onClick: createConsoleShortcut,
|
||||
}) : null,
|
||||
|
||||
// Connect button
|
||||
createButton({
|
||||
$connect = createButton({
|
||||
classes: ['bx-remote-play-connect-button'],
|
||||
label: t('console-connect'),
|
||||
style: ButtonStyle.PRIMARY | ButtonStyle.FOCUSABLE,
|
||||
@ -92,6 +122,10 @@ export class RemotePlayDialog extends NavigationDialog {
|
||||
}),
|
||||
);
|
||||
|
||||
setNearby($child, {
|
||||
orientation: 'horizontal',
|
||||
focus: $connect,
|
||||
})
|
||||
$fragment.appendChild($child);
|
||||
}
|
||||
|
||||
@ -130,7 +164,7 @@ export class RemotePlayDialog extends NavigationDialog {
|
||||
}
|
||||
|
||||
focusIfNeeded(): void {
|
||||
const $btnConnect = this.$container.querySelector<HTMLElement>('.bx-remote-play-device-wrapper button');
|
||||
const $btnConnect = this.$container.querySelector<HTMLElement>('.bx-remote-play-device-wrapper button:last-of-type');
|
||||
$btnConnect && $btnConnect.focus();
|
||||
}
|
||||
}
|
||||
|
@ -191,7 +191,6 @@ export class SettingsDialog extends NavigationDialog {
|
||||
},
|
||||
GlobalPref.SERVER_BYPASS_RESTRICTION,
|
||||
GlobalPref.UI_CONTROLLER_FRIENDLY,
|
||||
GlobalPref.REMOTE_PLAY_ENABLED,
|
||||
],
|
||||
}, {
|
||||
group: 'server',
|
||||
@ -374,7 +373,7 @@ export class SettingsDialog extends NavigationDialog {
|
||||
$parent => {
|
||||
$parent.appendChild(createButton({
|
||||
label: t('clear-data'),
|
||||
style: ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||
style: ButtonStyle.DANGER | ButtonStyle.FROSTED | ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||
onClick: e => {
|
||||
if (confirm(t('clear-data-confirm'))) {
|
||||
clearAllData();
|
||||
@ -453,6 +452,7 @@ export class SettingsDialog extends NavigationDialog {
|
||||
StreamPref.VIDEO_MAX_FPS,
|
||||
StreamPref.VIDEO_POWER_PREFERENCE,
|
||||
StreamPref.VIDEO_PROCESSING,
|
||||
StreamPref.VIDEO_PROCESSING_MODE,
|
||||
StreamPref.VIDEO_RATIO,
|
||||
StreamPref.VIDEO_POSITION,
|
||||
StreamPref.VIDEO_SHARPNESS,
|
||||
|
@ -11,6 +11,8 @@ import { GlobalPref } from "@/enums/pref-keys";
|
||||
import { getGlobalPref } from "@/utils/pref-utils";
|
||||
import { BxLogger } from "@/utils/bx-logger";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
import { BlockFeature } from "@/enums/pref-values";
|
||||
import { Toast } from "@/utils/toast";
|
||||
|
||||
export class HeaderSection {
|
||||
private static instance: HeaderSection;
|
||||
@ -44,7 +46,7 @@ export class HeaderSection {
|
||||
});
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
@ -59,6 +61,8 @@ export class HeaderSection {
|
||||
if (!SCRIPT_VERSION.includes('beta') && PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) {
|
||||
$btnSettings.setAttribute('data-update-available', 'true');
|
||||
}
|
||||
} else if (status === 'error') {
|
||||
Toast.show(t('server-list-error'), '❌', { instant: true });
|
||||
} else if (status === 'unavailable') {
|
||||
STATES.supportedRegion = false;
|
||||
|
||||
@ -74,7 +78,12 @@ export class HeaderSection {
|
||||
}
|
||||
|
||||
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) {
|
||||
$target = document.querySelector('div[class^=UnsupportedMarketPage-module__buttons]');
|
||||
}
|
||||
|
24
src/types/global.d.ts
vendored
24
src/types/global.d.ts
vendored
@ -11,7 +11,28 @@ export {};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
AppInterface: any;
|
||||
AppInterface: {
|
||||
startPointerServer(),
|
||||
requestPointerCapture(),
|
||||
releasePointerCapture(),
|
||||
|
||||
runShortcut?(action: string),
|
||||
saveScreenshot(name: string | undefined, data: string),
|
||||
vibrate(dataJson: string, intensity: number),
|
||||
openTrueAchievementsLink(override: boolean, xboxTitleId?: string | undefined, id?: string | undefined),
|
||||
|
||||
openAppSettings?(),
|
||||
updateLatestScript(),
|
||||
closeApp(),
|
||||
getDeepLinkData(): string,
|
||||
|
||||
createShortcut(path: string),
|
||||
createConsoleShortcut(serverId: string, deviceName: string, optionsJson: string),
|
||||
downloadWallpapers(titleSlug: string | undefined, productId: string | undefined),
|
||||
|
||||
onEvent(event: String),
|
||||
onEventBus(event: String),
|
||||
};
|
||||
BX_FLAGS?: BxFlags;
|
||||
BX_CE: (elmName: string, props: { [index: string]: any }={}) => HTMLElement;
|
||||
BX_EXPOSED: typeof BxExposed & Partial<{
|
||||
@ -26,7 +47,6 @@ declare global {
|
||||
touchLayoutManager: any;
|
||||
}>;
|
||||
|
||||
BX_REMOTE_PLAY_CONFIG: BxStates.remotePlay.config;
|
||||
BX_STREAM_SETTINGS: StreamSettingsData;
|
||||
|
||||
BX_FETCH: typeof window['fetch'];
|
||||
|
2
src/types/index.d.ts
vendored
2
src/types/index.d.ts
vendored
@ -135,7 +135,7 @@ type XboxAchievement = {
|
||||
}
|
||||
};
|
||||
|
||||
type OsName = 'windows' | 'tizen' | 'android';
|
||||
type OsName = 'windows' | 'tizen' | 'webOS' | 'xboxOS' | 'android';
|
||||
|
||||
type XcloudGamepad = {
|
||||
GamepadIndex: number;
|
||||
|
4
src/types/states.d.ts
vendored
4
src/types/states.d.ts
vendored
@ -41,11 +41,7 @@ type BxStates = {
|
||||
}>;
|
||||
|
||||
remotePlay: Partial<{
|
||||
isPlaying: boolean;
|
||||
server: string;
|
||||
config: {
|
||||
serverId: string;
|
||||
};
|
||||
titleId?: string;
|
||||
}>;
|
||||
|
||||
|
3
src/types/stream.d.ts
vendored
3
src/types/stream.d.ts
vendored
@ -1,7 +1,8 @@
|
||||
import type { StreamVideoProcessing } from "@/enums/pref-values";
|
||||
import type { StreamVideoProcessing, StreamVideoProcessingMode } from "@/enums/pref-values";
|
||||
|
||||
type StreamPlayerOptions = {
|
||||
processing: StreamVideoProcessing,
|
||||
processingMode: StreamVideoProcessingMode,
|
||||
sharpness: number,
|
||||
saturation: number,
|
||||
contrast: number,
|
||||
|
@ -9,7 +9,7 @@ type EventCallback<T = any> = (payload: T) => void;
|
||||
|
||||
export type ScriptEvents = {
|
||||
'xcloud.server': {
|
||||
status: 'ready' | 'unavailable' | 'signed-out',
|
||||
status: 'ready' | 'unavailable' | 'signed-out' | 'error',
|
||||
};
|
||||
|
||||
'dialog.shown': {};
|
||||
@ -152,7 +152,10 @@ export class BxEventBus<TEvents extends Record<string, any>> {
|
||||
try {
|
||||
if (event in this.appJsInterfaces) {
|
||||
const method = this.appJsInterfaces[event];
|
||||
AppInterface[method] && AppInterface[method]();
|
||||
if (method && method in AppInterface) {
|
||||
// @ts-ignore
|
||||
AppInterface[method]();
|
||||
}
|
||||
} else {
|
||||
AppInterface.onEventBus(this.group + '.' + (event as string));
|
||||
}
|
||||
|
56
src/utils/deep-link.ts
Normal file
56
src/utils/deep-link.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { localRedirect } from "@/modules/ui/ui";
|
||||
import { AppInterface } from "./global";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
import { BxEvent } from "./bx-event";
|
||||
|
||||
|
||||
export function handleDeepLink() {
|
||||
const deepLinkData = JSON.parse(AppInterface.getDeepLinkData());
|
||||
console.log('deepLinkData', deepLinkData);
|
||||
if (!deepLinkData.host) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onReady = () => {
|
||||
if (deepLinkData.host === 'PLAY') {
|
||||
localRedirect('/launch/' + deepLinkData.data.join('/'));
|
||||
} else if (deepLinkData.host === 'DEVICE_CODE') {
|
||||
localRedirect('/login/deviceCode');
|
||||
} else if (deepLinkData.host === 'REMOTE_PLAY') {
|
||||
const serverId = deepLinkData.data[0];
|
||||
const resolution = deepLinkData.data[1] || '1080p';
|
||||
|
||||
const manager = RemotePlayManager.getInstance();
|
||||
if (!manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (manager.isReady()) {
|
||||
manager.play(serverId, resolution);
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener(BxEvent.REMOTE_PLAY_READY, () => {
|
||||
manager.play(serverId, resolution);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let handled = false
|
||||
const observer = new MutationObserver(mutationList => {
|
||||
mutationList.forEach(mutation => {
|
||||
if (handled || mutation.type !== 'childList') {
|
||||
return;
|
||||
}
|
||||
|
||||
const $target = mutation.target as HTMLElement;
|
||||
if (!handled && $target.className && $target.className['startsWith'] && $target.className.includes('HomePage-module__homePage')) {
|
||||
handled = true;
|
||||
observer.disconnect();
|
||||
setTimeout(onReady, 1000);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(document.documentElement, {subtree: true, childList: true});
|
||||
}
|
@ -10,8 +10,8 @@ export let FeatureGates: { [key: string]: boolean } = {
|
||||
ShowForcedUpdateScreen: false,
|
||||
EnableTakControlResizing: true, // Experimenting
|
||||
EnableLazyLoadedHome: false,
|
||||
EnableRemotePlay: getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED),
|
||||
EnableConsoles: 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
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { LoadingScreen } from "@modules/loading-screen";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
|
||||
|
||||
@ -26,10 +23,6 @@ export function onHistoryChanged(e: PopStateEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFullVersion()) {
|
||||
window.setTimeout(RemotePlayManager.detect, 10);
|
||||
}
|
||||
|
||||
// Hide Navigation dialog
|
||||
NavigationDialogManager.getInstance().hide();
|
||||
|
||||
|
@ -22,6 +22,7 @@ export function patchVideoApi() {
|
||||
|
||||
const playerOptions = {
|
||||
processing: getStreamPref(StreamPref.VIDEO_PROCESSING),
|
||||
processingMode: getStreamPref(StreamPref.VIDEO_PROCESSING_MODE),
|
||||
sharpness: getStreamPref(StreamPref.VIDEO_SHARPNESS),
|
||||
saturation: getStreamPref(StreamPref.VIDEO_SATURATION),
|
||||
contrast: getStreamPref(StreamPref.VIDEO_CONTRAST),
|
||||
|
@ -109,7 +109,7 @@ export async function patchIceCandidates(request: Request, consoleAddrs?: Remote
|
||||
}
|
||||
|
||||
const options = {
|
||||
preferIpv6Server: getGlobalPref(GlobalPref.SERVER_PREFER_IPV6),
|
||||
preferIpv6Server: getGlobalPref(consoleAddrs ? GlobalPref.REMOTE_PLAY_PREFER_IPV6 : GlobalPref.SERVER_PREFER_IPV6),
|
||||
consoleAddrs: consoleAddrs,
|
||||
};
|
||||
|
||||
@ -297,7 +297,7 @@ export function interceptHttpRequests() {
|
||||
}
|
||||
|
||||
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';
|
||||
} else {
|
||||
requestType = 'xcloud';
|
||||
|
@ -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 { GhPagesUtils } from "../gh-pages";
|
||||
import { BxEventBus } from "../bx-event-bus";
|
||||
import { BxIcon } from "../bx-icon";
|
||||
|
||||
|
||||
function getSupportedCodecProfiles() {
|
||||
@ -497,6 +496,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage<GlobalPref> {
|
||||
[BlockFeature.BYOG]: t('stream-your-own-game'),
|
||||
[BlockFeature.NOTIFICATIONS_INVITES]: t('notifications') + ': ' + t('invites'),
|
||||
[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,
|
||||
},
|
||||
|
||||
[GlobalPref.REMOTE_PLAY_ENABLED]: {
|
||||
requiredVariants: 'full',
|
||||
label: t('enable-remote-play-feature'),
|
||||
labelIcon: BxIcon.REMOTE_PLAY,
|
||||
default: false,
|
||||
},
|
||||
|
||||
[GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION]: {
|
||||
requiredVariants: 'full',
|
||||
default: StreamResolution.DIM_1080P,
|
||||
@ -542,6 +535,11 @@ export class GlobalSettingsStorage extends BaseSettingsStorage<GlobalPref> {
|
||||
},
|
||||
},
|
||||
|
||||
[GlobalPref.REMOTE_PLAY_PREFER_IPV6]: {
|
||||
requiredVariants: 'full',
|
||||
default: false,
|
||||
},
|
||||
|
||||
[GlobalPref.GAME_FORTNITE_FORCE_CONSOLE]: {
|
||||
requiredVariants: 'full',
|
||||
label: '🎮 ' + t('fortnite-force-console-version'),
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table";
|
||||
import { MkbMappingDefaultPresetId } from "../local-db/mkb-mapping-presets-table";
|
||||
@ -179,6 +179,18 @@ export class StreamSettingsStorage extends BaseSettingsStorage<StreamPref> {
|
||||
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]: {
|
||||
label: t('renderer-configuration'),
|
||||
default: VideoPowerPreference.DEFAULT,
|
||||
@ -228,10 +240,13 @@ export class StreamSettingsStorage extends BaseSettingsStorage<StreamPref> {
|
||||
default: VideoRatio['16:9'],
|
||||
options: {
|
||||
[VideoRatio['16:9']]: `16:9 (${t('default')})`,
|
||||
[VideoRatio['18:9']]: '18:9',
|
||||
[VideoRatio['21:9']]: '21:9',
|
||||
[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['5:4']]: '5:4',
|
||||
|
||||
[VideoRatio.FILL]: t('stretch'),
|
||||
//'cover': 'Cover',
|
||||
|
@ -61,12 +61,11 @@ const Texts = {
|
||||
"browser-unsupported-feature": "Your browser doesn't support this feature",
|
||||
"button-xbox": "Xbox button",
|
||||
"bypass-region-restriction": "Bypass region restriction",
|
||||
"can-stream-xbox-360-games": "Can stream Xbox 360 games",
|
||||
"cancel": "Cancel",
|
||||
"cant-stream-xbox-360-games": "Can't stream Xbox 360 games",
|
||||
"center": "Center",
|
||||
"chat": "Chat",
|
||||
"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",
|
||||
"clear": "Clear",
|
||||
"clear-data": "Clear data",
|
||||
@ -229,6 +228,7 @@ const Texts = {
|
||||
"only-supports-some-games": "Only supports some games",
|
||||
"opacity": "Opacity",
|
||||
"other": "Other",
|
||||
"performance": "Performance",
|
||||
"playing": "Playing",
|
||||
"playtime": "Playtime",
|
||||
"poland": "Poland",
|
||||
@ -266,6 +266,7 @@ const Texts = {
|
||||
],
|
||||
"press-to-bind": "Press a key or do a mouse click to bind...",
|
||||
"prompt-preset-name": "Preset's name:",
|
||||
"quality": "Quality",
|
||||
"recommended": "Recommended",
|
||||
"recommended-settings-for-device": [
|
||||
(e: any) => `Recommended settings for ${e.device}`,
|
||||
@ -321,6 +322,7 @@ const Texts = {
|
||||
"separate-touch-controller": "Separate Touch controller & Controller #1",
|
||||
"separate-touch-controller-note": "Touch controller is Player 1, Controller #1 is Player 2",
|
||||
"server": "Server",
|
||||
"server-list-error": "Can't get the server list",
|
||||
"server-locations": "Server locations",
|
||||
"settings": "Settings",
|
||||
"settings-for": "Settings for",
|
||||
@ -370,6 +372,7 @@ const Texts = {
|
||||
"tc-custom-layout-style": "Custom layout's button style",
|
||||
"tc-muted-colors": "Muted colors",
|
||||
"tc-standard-layout-style": "Standard layout's button style",
|
||||
"test-controller": "Test controller",
|
||||
"text-size": "Text size",
|
||||
"theme": "Theme",
|
||||
"toggle": "Toggle",
|
||||
@ -435,6 +438,8 @@ const Texts = {
|
||||
"wallpaper": "Wallpaper",
|
||||
"webgl2": "WebGL2",
|
||||
"webgpu": "WebGPU",
|
||||
"xbox-360-games": "Xbox 360 games",
|
||||
"xbox-apps": "Xbox apps",
|
||||
};
|
||||
|
||||
export class Translations {
|
||||
|
@ -153,7 +153,7 @@ export class TrueAchievements {
|
||||
xboxTitleId = this.getStreamXboxTitleId();
|
||||
}
|
||||
|
||||
if (AppInterface && AppInterface.openTrueAchievementsLink) {
|
||||
if (AppInterface?.openTrueAchievementsLink) {
|
||||
AppInterface.openTrueAchievementsLink(override, xboxTitleId?.toString(), id?.toString());
|
||||
return;
|
||||
}
|
||||
|
@ -136,7 +136,8 @@ export function parseDetailsPath(path: string) {
|
||||
|
||||
export function clearAllData() {
|
||||
// Delete localStorage items
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
|
||||
for (let i = localStorage.length - 1; i >= 0; i--) {
|
||||
const key = localStorage.key(i);
|
||||
if (!key) {
|
||||
continue;
|
||||
|
@ -49,7 +49,13 @@ export class XcloudInterceptor {
|
||||
ip && (request as Request).headers.set('X-Forwarded-For', ip);
|
||||
}
|
||||
|
||||
const response = await NATIVE_FETCH(request, init);
|
||||
let response;
|
||||
try {
|
||||
response = await NATIVE_FETCH(request, init);
|
||||
} catch (e) {
|
||||
BxEventBus.Script.emit('xcloud.server', { status: 'error' });
|
||||
return;
|
||||
}
|
||||
if (response.status !== 200) {
|
||||
// Unsupported region
|
||||
BxEventBus.Script.emit('xcloud.server', { status: 'unavailable' });
|
||||
|
@ -2,10 +2,8 @@ import { TouchController } from "@/modules/touch-controller";
|
||||
import { BxEvent } from "./bx-event";
|
||||
import { SupportedInputType } from "./bx-exposed";
|
||||
import { NATIVE_FETCH } from "./bx-flags";
|
||||
import { STATES } from "./global";
|
||||
import { generateMsDeviceInfo, getOsNameFromResolution, patchIceCandidates } from "./network";
|
||||
import { GlobalPref } from "@/enums/pref-keys";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
import { TouchControllerMode } from "@/enums/pref-values";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
import { getGlobalPref } from "./pref-utils";
|
||||
@ -111,7 +109,6 @@ export class XhomeInterceptor {
|
||||
for (const pair of (clone.headers as any).entries()) {
|
||||
headers[pair[0]] = pair[1];
|
||||
}
|
||||
headers.authorization = `Bearer ${RemotePlayManager.getInstance()!.getXcloudToken()}`;
|
||||
|
||||
const index = request.url.indexOf('.xboxlive.com');
|
||||
request = new Request('https://wus.core.gssv-play-prod' + request.url.substring(index), {
|
||||
@ -147,8 +144,6 @@ export class XhomeInterceptor {
|
||||
for (const pair of (clone.headers as any).entries()) {
|
||||
headers[pair[0]] = pair[1];
|
||||
}
|
||||
// Add xHome token to headers
|
||||
headers.authorization = `Bearer ${RemotePlayManager.getInstance()!.getXhomeToken()}`;
|
||||
|
||||
// Patch resolution
|
||||
const osName = getOsNameFromResolution(getGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION));
|
||||
@ -164,12 +159,7 @@ export class XhomeInterceptor {
|
||||
opts.body = await clone.text();
|
||||
}
|
||||
|
||||
// Replace xCloud domain with xHome domain
|
||||
let url = request.url;
|
||||
if (!url.includes('/servers/home')) {
|
||||
const parsed = new URL(url);
|
||||
url = STATES.remotePlay.server + parsed.pathname;
|
||||
}
|
||||
const url = request.url;
|
||||
|
||||
// Create new Request instance
|
||||
request = new Request(url, opts);
|
||||
|
Reference in New Issue
Block a user