mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-01 20:01:44 +02:00
Compare commits
65 Commits
Author | SHA1 | Date | |
---|---|---|---|
c2f9f129d0 | |||
aa50261726 | |||
bb32d97ae8 | |||
3d2abf6b12 | |||
4c8a49a43a | |||
256f28695e | |||
9e851fbd15 | |||
c829f74dcc | |||
62cf045f05 | |||
fdb4e58b5d | |||
b1407c2447 | |||
b5ba6e9600 | |||
a3094d2c9f | |||
3290a36886 | |||
e502e49d64 | |||
604cf7094a | |||
3bfa7e5f21 | |||
e3789b4fb7 | |||
0551d909e5 | |||
da6ab51ba0 | |||
4a65221ad0 | |||
528c6774fe | |||
3c8a35d441 | |||
544ededb64 | |||
f4f88f688b | |||
1fb1a64767 | |||
769649a376 | |||
057adb62df | |||
98e8ff4783 | |||
f5e1b0a9fa | |||
8ea3503dd3 | |||
b733d55e9e | |||
317ac9017b | |||
b8c62a1f4d | |||
7332528f72 | |||
d063500aae | |||
29ff1bc09c | |||
8998daf14c | |||
8bdad8b319 | |||
5dd3ebdea1 | |||
55d7796f96 | |||
0b02a758db | |||
3b2abbf6bb | |||
43a66db697 | |||
a3130101f4 | |||
3483672554 | |||
75d7443e0f | |||
b5d2d0fdec | |||
20afe92371 | |||
5738412f71 | |||
d2ee3d2122 | |||
a65fd8233b | |||
1375fb115d | |||
bedf82d363 | |||
b463e4f014 | |||
2f8c776133 | |||
585ee82776 | |||
3c2549178b | |||
2fb2cfb004 | |||
ac20cc51cc | |||
85339f09da | |||
4b06d9fcff | |||
d4c1e8cce3 | |||
cf1f656ecf | |||
2fd482bb7b |
44
bun.lock
44
bun.lock
@ -3,11 +3,11 @@
|
|||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.2.0",
|
"@types/bun": "^1.2.9",
|
||||||
"@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.24.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.10.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw=="],
|
"@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.13.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw=="],
|
||||||
|
|
||||||
"@eslint/js": ["@eslint/js@9.19.0", "", {}, "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ=="],
|
"@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.25.0", "", {}, "sha512-iWhsUS8Wgxz9AXNfvfOPFSW4VfMXdVhp1hjkZVhXCrpgh/aLcc45rX6MPu+tIVUWDw0HfNwth7O28M1xDxNf9w=="],
|
||||||
|
|
||||||
"@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.2.8", "", { "dependencies": { "@eslint/core": "^0.13.0", "levn": "^0.4.1" } }, "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA=="],
|
||||||
|
|
||||||
"@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.0", "", { "dependencies": { "bun-types": "1.2.0" } }, "sha512-5N1JqdahfpBlAv4wy6svEYcd/YfO2GNrbL95JOmFx8nkE6dbK4R0oSE5SpBA4vBRqgrOUAXF8Dpiz+gi7r80SA=="],
|
"@types/bun": ["@types/bun@1.2.10", "", { "dependencies": { "bun-types": "1.2.10" } }, "sha512-eilv6WFM3M0c9ztJt7/g80BDusK98z/FrFwseZgT4bXCq2vPhXD4z8R3oddmAn+R/Nmz9vBn4kweJKmGTZj+lg=="],
|
||||||
|
|
||||||
"@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.10.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww=="],
|
"@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="],
|
||||||
|
|
||||||
"@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.60", "", {}, "sha512-8B/tdfRFKdrnejqmvq95ogp8tf52oZ51p3f4QD5m5Paey/qlX4Rhhy5Y8tgFMi7Ms70HzcMMw3EQjH/jdhTwlA=="],
|
||||||
|
|
||||||
"@webgpu/types": ["@webgpu/types@0.1.53", "", {}, "sha512-x+BLw/opaz9LiVyrMsP75nO1Rg0QfrACUYIbVSfGwY/w0DiWIPYYrpte6us//KZXinxFAOJl0+C17L1Vi2vmDw=="],
|
|
||||||
|
|
||||||
"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.0", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-KEaJxyZfbV/c4eyG0vyehDpYmBGreNiQbZIqvVHJwZ4BmeuWlNZ7EAzMN2Zcd7ailmS/tGVW0BgYbGf+lGEpWw=="],
|
"bun-types": ["bun-types@1.2.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-b5ITZMnVdf3m1gMvJHG+gIfeJHiQPJak0f7925Hxu6ZN5VKA8AGy4GZ4lM+Xkn6jtWxg5S3ldWvfmXdvnkp3GQ=="],
|
||||||
|
|
||||||
"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.19.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.19.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-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA=="],
|
"eslint": ["eslint@9.25.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.13.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.25.0", "@eslint/plugin-kit": "^0.2.8", "@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-MsBdObhM4cEwkzCiraDv7A6txFXEqtNXOb877TsSp2FCkBNl8JfVQrmiuDqC1IkejT6JLPzYBXx/xAiYhyzgGA=="],
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
@ -279,8 +279,6 @@
|
|||||||
|
|
||||||
"@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=="],
|
||||||
@ -301,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=="],
|
||||||
|
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Better xCloud
|
// @name Better xCloud
|
||||||
// @namespace https://github.com/redphx
|
// @namespace https://github.com/redphx
|
||||||
// @version 6.4.0
|
// @version 6.6.0
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
1006
dist/better-xcloud.pretty.user.js
vendored
1006
dist/better-xcloud.pretty.user.js
vendored
File diff suppressed because one or more lines are too long
234
dist/better-xcloud.user.js
vendored
234
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -10,11 +10,11 @@
|
|||||||
"build": "build.ts"
|
"build": "build.ts"
|
||||||
},
|
},
|
||||||
"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"
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +202,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.bx-settings-important-row {
|
||||||
|
background: #733b00;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bx-settings-dialog-note {
|
.bx-settings-dialog-note {
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
74
src/index.ts
74
src/index.ts
@ -33,12 +33,9 @@ import { GameTile } from "./modules/ui/game-tile";
|
|||||||
import { ProductDetailsPage } from "./modules/ui/product-details";
|
import { ProductDetailsPage } from "./modules/ui/product-details";
|
||||||
import { NavigationDialogManager } from "./modules/ui/dialog/navigation-dialog";
|
import { NavigationDialogManager } from "./modules/ui/dialog/navigation-dialog";
|
||||||
import { GlobalPref, StreamPref } from "./enums/pref-keys";
|
import { GlobalPref, StreamPref } from "./enums/pref-keys";
|
||||||
import { SettingsDialog } from "./modules/ui/dialog/settings-dialog";
|
|
||||||
import { StreamUiHandler } from "./modules/stream/stream-ui";
|
|
||||||
import { UserAgent } from "./utils/user-agent";
|
import { UserAgent } from "./utils/user-agent";
|
||||||
import { XboxApi } from "./utils/xbox-api";
|
import { XboxApi } from "./utils/xbox-api";
|
||||||
import { StreamStatsCollector } from "./utils/stream-stats-collector";
|
import { StreamStatsCollector } from "./utils/stream-stats-collector";
|
||||||
import { RootDialogObserver } from "./utils/root-dialog-observer";
|
|
||||||
import { StreamSettings } from "./utils/stream-settings";
|
import { StreamSettings } from "./utils/stream-settings";
|
||||||
import { KeyboardShortcutHandler } from "./modules/mkb/keyboard-shortcut-handler";
|
import { KeyboardShortcutHandler } from "./modules/mkb/keyboard-shortcut-handler";
|
||||||
import { GhPagesUtils } from "./utils/gh-pages";
|
import { GhPagesUtils } from "./utils/gh-pages";
|
||||||
@ -48,6 +45,8 @@ import { getGlobalPref, getStreamPref } from "./utils/pref-utils";
|
|||||||
import { SettingsManager } from "./modules/settings-manager";
|
import { SettingsManager } from "./modules/settings-manager";
|
||||||
import { Toast } from "./utils/toast";
|
import { Toast } from "./utils/toast";
|
||||||
import { WebGPUPlayer } from "./modules/player/webgpu/webgpu-player";
|
import { WebGPUPlayer } from "./modules/player/webgpu/webgpu-player";
|
||||||
|
import { StreamUiHandler } from "./modules/stream/stream-ui";
|
||||||
|
import { TrueAchievements } from "./utils/true-achievements";
|
||||||
|
|
||||||
SettingsManager.getInstance();
|
SettingsManager.getInstance();
|
||||||
|
|
||||||
@ -148,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(() => {
|
||||||
@ -174,7 +179,7 @@ document.addEventListener('readystatechange', e => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Show Settings button in the header when not signed in
|
// Show Settings button in the header when not signed in
|
||||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
// window.setTimeout(HeaderSection.watchHeader, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide "Play with Friends" skeleton section
|
// Hide "Play with Friends" skeleton section
|
||||||
@ -198,26 +203,14 @@ window.addEventListener('popstate', onHistoryChanged);
|
|||||||
window.history.pushState = patchHistoryMethod('pushState');
|
window.history.pushState = patchHistoryMethod('pushState');
|
||||||
window.history.replaceState = patchHistoryMethod('replaceState');
|
window.history.replaceState = patchHistoryMethod('replaceState');
|
||||||
|
|
||||||
BxEventBus.Script.once('xcloud.server.unavailable', () => {
|
BxEventBus.Script.on('ui.header.rendered', () => {
|
||||||
STATES.supportedRegion = false;
|
HeaderSection.getInstance().checkHeader();
|
||||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
|
||||||
|
|
||||||
// Open Settings dialog on Unsupported page
|
|
||||||
const $unsupportedPage = document.querySelector<HTMLElement>('div[class^=UnsupportedMarketPage-module__container]');
|
|
||||||
if ($unsupportedPage) {
|
|
||||||
SettingsDialog.getInstance().show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
BxEventBus.Script.on('xcloud.server.ready', () => {
|
|
||||||
STATES.isSignedIn = true;
|
|
||||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
BxEventBus.Stream.on('state.loading', () => {
|
BxEventBus.Stream.on('state.loading', () => {
|
||||||
// Get title ID for screenshot's name
|
// Get title ID for screenshot's name
|
||||||
if (window.location.pathname.includes('/launch/') && STATES.currentStream.titleInfo) {
|
if (window.location.pathname.includes('/launch/') && STATES.currentStream.titleInfo) {
|
||||||
STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.product.title);
|
STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.productInfo.title);
|
||||||
} else {
|
} else {
|
||||||
STATES.currentStream.titleSlug = 'remote-play';
|
STATES.currentStream.titleSlug = 'remote-play';
|
||||||
}
|
}
|
||||||
@ -247,7 +240,6 @@ BxEventBus.Stream.on('state.playing', payload => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
STATES.isPlaying = true;
|
STATES.isPlaying = true;
|
||||||
StreamUiHandler.observe();
|
|
||||||
|
|
||||||
if (isFullVersion()) {
|
if (isFullVersion()) {
|
||||||
const gameBar = GameBar.getInstance();
|
const gameBar = GameBar.getInstance();
|
||||||
@ -274,10 +266,39 @@ BxEventBus.Stream.on('state.playing', payload => {
|
|||||||
updateVideoPlayer();
|
updateVideoPlayer();
|
||||||
});
|
});
|
||||||
|
|
||||||
BxEventBus.Stream.on('state.error', () => {
|
BxEventBus.Script.on('ui.error.rendered', () => {
|
||||||
BxEventBus.Stream.emit('state.stopped', {});
|
BxEventBus.Stream.emit('state.stopped', {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
BxEventBus.Script.on('ui.guideHome.rendered', () => {
|
||||||
|
const $root = document.querySelector<HTMLElement>('#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]');
|
||||||
|
$root && GuideMenu.getInstance().injectHome($root, STATES.isPlaying);
|
||||||
|
});
|
||||||
|
|
||||||
|
BxEventBus.Script.on('ui.guideAchievementProgress.rendered', () => {
|
||||||
|
const $elm = document.querySelector('#gamepass-dialog-root button[class*=AchievementsButton-module__progressBarContainer]');
|
||||||
|
if ($elm) {
|
||||||
|
TrueAchievements.getInstance().injectAchievementsProgress($elm as HTMLElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
BxEventBus.Script.on('ui.guideAchievementDetail.rendered', () => {
|
||||||
|
const $elm = document.querySelector('#gamepass-dialog-root div[class^=AchievementDetailPage-module]');
|
||||||
|
if ($elm) {
|
||||||
|
TrueAchievements.getInstance().injectAchievementDetailPage($elm as HTMLElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
BxEventBus.Stream.on('ui.streamMenu.rendered', async () => {
|
||||||
|
await StreamUiHandler.handleStreamMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
BxEventBus.Stream.on('ui.streamHud.rendered', async () => {
|
||||||
|
const $elm = document.querySelector<HTMLElement>('#StreamHud');
|
||||||
|
$elm && StreamUiHandler.handleSystemMenu($elm);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
isFullVersion() && window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
|
isFullVersion() && window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
|
||||||
const component = (e as any).component;
|
const component = (e as any).component;
|
||||||
if (component === 'product-detail') {
|
if (component === 'product-detail') {
|
||||||
@ -308,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);
|
||||||
@ -337,6 +358,7 @@ function unload() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BxLogger.warning('Unloading');
|
||||||
if (isFullVersion()) {
|
if (isFullVersion()) {
|
||||||
KeyboardShortcutHandler.getInstance().stop();
|
KeyboardShortcutHandler.getInstance().stop();
|
||||||
|
|
||||||
@ -406,12 +428,9 @@ function main() {
|
|||||||
disableAdobeAudienceManager();
|
disableAdobeAudienceManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
RootDialogObserver.waitForRootDialog();
|
|
||||||
|
|
||||||
// Setup UI
|
// Setup UI
|
||||||
addCss();
|
addCss();
|
||||||
|
|
||||||
GuideMenu.getInstance().addEventListeners();
|
|
||||||
StreamStatsCollector.setupEvents();
|
StreamStatsCollector.setupEvents();
|
||||||
StreamBadges.setupEvents();
|
StreamBadges.setupEvents();
|
||||||
StreamStats.setupEvents();
|
StreamStats.setupEvents();
|
||||||
@ -429,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();
|
||||||
}
|
}
|
||||||
|
@ -25,3 +25,17 @@ export const renderStylus = async () => {
|
|||||||
export const compressCss = (css: string) => {
|
export const compressCss = (css: string) => {
|
||||||
return (stylus(css, {}).set('compress', true)).render();
|
return (stylus(css, {}).set('compress', true)).render();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const compressCode = (code: string): string => {
|
||||||
|
return code.split('\n') // Split into lines
|
||||||
|
.map(line => line.startsWith('#') || line.startsWith('@') ? line + '\n' : line.trim()) // Trim spaces, with exceptions for shader files
|
||||||
|
.filter(line => line && !line.startsWith('//')) // Remove empty and commented lines
|
||||||
|
.join(''); // Join into a single line
|
||||||
|
};
|
||||||
|
|
||||||
|
export const compressCodeFile = async (path: string) => {
|
||||||
|
const file = Bun.file(path);
|
||||||
|
const code = await file.text();
|
||||||
|
|
||||||
|
return compressCode(code);
|
||||||
|
};
|
||||||
|
@ -35,7 +35,9 @@ export class LoadingScreen {
|
|||||||
LoadingScreen.$bgStyle = $bgStyle;
|
LoadingScreen.$bgStyle = $bgStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadingScreen.setBackground(titleInfo.product.heroImageUrl || titleInfo.product.titledHeroImageUrl || titleInfo.product.tileImageUrl);
|
if (titleInfo.productInfo) {
|
||||||
|
LoadingScreen.setBackground(titleInfo.productInfo.heroImageUrl || titleInfo.productInfo.titledHeroImageUrl || titleInfo.productInfo.tileImageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
if (getGlobalPref(GlobalPref.LOADING_SCREEN_ROCKET) === LoadingScreenRocket.HIDE) {
|
if (getGlobalPref(GlobalPref.LOADING_SCREEN_ROCKET) === LoadingScreenRocket.HIDE) {
|
||||||
LoadingScreen.hideRocket();
|
LoadingScreen.hideRocket();
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { ScriptEvents, StreamEvents } from "@/utils/bx-event-bus";
|
||||||
import type { PatchArray, PatchName, PatchPage } from "./patcher";
|
import type { PatchArray, PatchName, PatchPage } from "./patcher";
|
||||||
|
|
||||||
export class PatcherUtils {
|
export class PatcherUtils {
|
||||||
@ -35,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 filterPatches(patches: Array<string | false>): PatchArray {
|
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);
|
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;
|
||||||
}
|
}
|
||||||
@ -96,4 +103,11 @@ 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, separator: string = ';') {
|
||||||
|
const newCode = `window.BX_EXPOSED.reactUseEffect(() => window.BxEventBus.${group}.emit('${eventName}', {}), [])${separator}`;
|
||||||
|
str = PatcherUtils.insertAt(str, index, newCode);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,16 +11,18 @@ import codeGameCardIcons from "./patches/game-card-icons.js" with { type: "text"
|
|||||||
import codeLocalCoOpEnable from "./patches/local-co-op-enable.js" with { type: "text" };
|
import codeLocalCoOpEnable from "./patches/local-co-op-enable.js" with { type: "text" };
|
||||||
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
|
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
|
||||||
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
|
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
|
||||||
|
import codeStreamHud from "./patches/stream-hud.js" with { type: "text" };
|
||||||
|
import codeCreatePortal from "./patches/create-portal.js" with { type: "text" };
|
||||||
import { GlobalPref, StorageKey } from "@/enums/pref-keys.js";
|
import { GlobalPref, StorageKey } from "@/enums/pref-keys.js";
|
||||||
import { getGlobalPref } from "@/utils/pref-utils.js";
|
import { getGlobalPref } from "@/utils/pref-utils.js";
|
||||||
import { GamePassCloudGallery } from "@/enums/game-pass-gallery";
|
import { GamePassCloudGallery } from "@/enums/game-pass-gallery";
|
||||||
import { t } from "@/utils/translation";
|
|
||||||
import { BlockFeature, NativeMkbMode, TouchControllerMode, UiLayout, UiSection } from "@/enums/pref-values";
|
import { BlockFeature, NativeMkbMode, TouchControllerMode, UiLayout, UiSection } from "@/enums/pref-values";
|
||||||
import { PatcherUtils } from "./patcher-utils.js";
|
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;
|
||||||
|
|
||||||
const LOG_TAG = 'Patcher';
|
const LOG_TAG = 'Patcher';
|
||||||
|
|
||||||
@ -29,11 +31,7 @@ const PATCHES = {
|
|||||||
disableAiTrack(str: string) {
|
disableAiTrack(str: string) {
|
||||||
let text = '.track=function(';
|
let text = '.track=function(';
|
||||||
const index = str.indexOf(text);
|
const index = str.indexOf(text);
|
||||||
if (index < 0) {
|
if (index < 0 || PatcherUtils.indexOf(str, '"AppInsightsCore', index, 200) < 0) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PatcherUtils.indexOf(str, '"AppInsightsCore', index, 200) < 0) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +39,7 @@ const PATCHES = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Set disableTelemetry() to true
|
// Set disableTelemetry() to true
|
||||||
|
/*
|
||||||
disableTelemetry(str: string) {
|
disableTelemetry(str: string) {
|
||||||
let text = '.disableTelemetry=function(){return!1}';
|
let text = '.disableTelemetry=function(){return!1}';
|
||||||
if (!str.includes(text)) {
|
if (!str.includes(text)) {
|
||||||
@ -49,6 +48,7 @@ const PATCHES = {
|
|||||||
|
|
||||||
return str.replace(text, '.disableTelemetry=function(){return!0}');
|
return str.replace(text, '.disableTelemetry=function(){return!0}');
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
|
|
||||||
disableTelemetryProvider(str: string) {
|
disableTelemetryProvider(str: string) {
|
||||||
let text = 'this.enableLightweightTelemetry=!';
|
let text = 'this.enableLightweightTelemetry=!';
|
||||||
@ -93,14 +93,14 @@ const PATCHES = {
|
|||||||
return str.replace(text, `?"${layout}":"${layout}"`);
|
return str.replace(text, `?"${layout}":"${layout}"`);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Replace "/direct-connect" with "/play"
|
remotePlayPostStreamRedirectUrl(str: string) {
|
||||||
remotePlayDirectConnectUrl(str: string) {
|
let text = '.RemotePlayRoot.getLink()):';
|
||||||
const index = str.indexOf('/direct-connect');
|
if (!str.includes(text)) {
|
||||||
if (index < 0) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return str.replace(str.substring(index - 9, index + 15), 'https://www.xbox.com/play');
|
str = str.replace(text, '.Home.getLink()):');
|
||||||
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
remotePlayKeepAlive(str: string) {
|
remotePlayKeepAlive(str: string) {
|
||||||
@ -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,33 +121,10 @@ 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);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Remote Play: Prevent adding "Fortnite" to the "Jump back in" list
|
|
||||||
remotePlayRecentlyUsedTitleIds(str: string) {
|
|
||||||
let text = '(e.data.recentlyUsedTitleIds)){';
|
|
||||||
if (!str.includes(text)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newCode = `if (window.BX_REMOTE_PLAY_CONFIG) return;`;
|
|
||||||
return str.replace(text, text + newCode);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Remote Play: change web page's title
|
|
||||||
remotePlayWebTitle(str: string) {
|
|
||||||
let text = 'titleTemplate:void 0,title:';
|
|
||||||
const index = str.indexOf(text);
|
|
||||||
if (index < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
str = PatcherUtils.insertAt(str, index + text.length, `!!window.BX_REMOTE_PLAY_CONFIG ? "${t('remote-play')} - Better xCloud" :`);
|
|
||||||
return str;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Block WebRTC stats collector
|
// Block WebRTC stats collector
|
||||||
blockWebRtcStatsCollector(str: string) {
|
blockWebRtcStatsCollector(str: string) {
|
||||||
let text = 'this.shouldCollectStats=!0';
|
let text = 'this.shouldCollectStats=!0';
|
||||||
@ -227,8 +192,11 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
|
|||||||
},
|
},
|
||||||
|
|
||||||
enableXcloudLogger(str: string) {
|
enableXcloudLogger(str: string) {
|
||||||
let text = 'this.telemetryProvider=e}log(e,t,r){';
|
let index = str.indexOf('this.telemetryProvider.trackErrorLike');
|
||||||
if (!str.includes(text)) {
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, '}log(', index, 1500));
|
||||||
|
index > -1 && (index = PatcherUtils.indexOf(str, '{', index, 30, true));
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +206,7 @@ const logFunc = [console.debug, console.log, console.warn, console.error][logLev
|
|||||||
logFunc(logTag, '//', logMessage);
|
logFunc(logTag, '//', logMessage);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
str = str.replaceAll(text, text + newCode);
|
str = PatcherUtils.insertAt(str, index, newCode);
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -421,25 +389,35 @@ if (titleInfo && !titleInfo.details.hasTouchSupport && !titleInfo.details.hasFak
|
|||||||
},
|
},
|
||||||
|
|
||||||
patchStreamHud(str: string) {
|
patchStreamHud(str: string) {
|
||||||
let text = 'let{onCollapse';
|
let index = str.indexOf('({onCollapse:');
|
||||||
if (!str.includes(text)) {
|
if (index < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newCode = `
|
try {
|
||||||
// Expose onShowStreamMenu
|
const canShowTakHUDVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'canShowTakHUD', index, 500, true) + 1);
|
||||||
window.BX_EXPOSED.showStreamMenu = e.onShowStreamMenu;
|
const guideUIVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'guideUI', index, 500, true) + 1);
|
||||||
// Restore the "..." button
|
const onShowStreamMenuVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'onShowStreamMenu', index, 500, true) + 1);
|
||||||
e.guideUI = null;
|
const offsetVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'offset', index, 500, true) + 1);
|
||||||
`;
|
|
||||||
|
|
||||||
// Remove the TAK Edit button when the touch controller is disabled
|
let newCode = renderString(codeStreamHud, {
|
||||||
if (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
|
guideUI: guideUIVar,
|
||||||
newCode += 'e.canShowTakHUD = false;';
|
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 = str.replace(text, newCode + text);
|
|
||||||
return str;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
broadcastPollingMode(str: string) {
|
broadcastPollingMode(str: string) {
|
||||||
@ -605,12 +583,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;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -701,9 +680,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;
|
||||||
@ -732,12 +711,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;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -928,8 +907,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;
|
||||||
@ -939,20 +918,6 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
|
|||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Optimize Game slug generator by using cached RegEx
|
|
||||||
optimizeGameSlugGenerator(str: string) {
|
|
||||||
let text = '/[;,/?:@&=+_`~$%#^*()!^\\u2122\\xae\\xa9]/g';
|
|
||||||
if (!str.includes(text)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
str = str.replace(text, 'window.BX_EXPOSED.GameSlugRegexes[0]');
|
|
||||||
str = str.replace('/ {2,}/g', 'window.BX_EXPOSED.GameSlugRegexes[1]');
|
|
||||||
str = str.replace('/ /g', 'window.BX_EXPOSED.GameSlugRegexes[2]');
|
|
||||||
|
|
||||||
return str;
|
|
||||||
},
|
|
||||||
|
|
||||||
modifyPreloadedState(str: string) {
|
modifyPreloadedState(str: string) {
|
||||||
let text = '=window.__PRELOADED_STATE__;';
|
let text = '=window.__PRELOADED_STATE__;';
|
||||||
if (!str.includes(text)) {
|
if (!str.includes(text)) {
|
||||||
@ -975,6 +940,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)) {
|
||||||
@ -1029,9 +998,14 @@ ${subsVar} = subs;
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newCode = 'window.BX_EXPOSED.reactCreateElement=';
|
str = PatcherUtils.insertAt(str, index - 1, 'window.BX_EXPOSED.reactCreateElement=');
|
||||||
str = PatcherUtils.insertAt(str, index - 1, newCode);
|
|
||||||
|
|
||||||
|
index = PatcherUtils.indexOf(str, '.useEffect=', index);
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = PatcherUtils.insertAt(str, index - 1, 'window.BX_EXPOSED.reactUseEffect=');
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1048,22 +1022,22 @@ ${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 productIdVar = PatcherUtils.getVariableNameAfter(str, productIdIndex + 11);
|
||||||
|
|
||||||
// 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 (!paramVar || !supportedInputIconsVar) {
|
if (!productIdVar || !supportedInputIconsVar) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newCode = renderString(codeGameCardIcons, {
|
const newCode = renderString(codeGameCardIcons, {
|
||||||
param: paramVar,
|
productId: productIdVar,
|
||||||
supportedInputIcons: supportedInputIconsVar,
|
supportedInputIcons: supportedInputIconsVar,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1130,99 +1104,242 @@ ${subsVar} = subs;
|
|||||||
|
|
||||||
str = PatcherUtils.insertAt(str, index, `&q=${getGlobalPref(GlobalPref.UI_IMAGE_QUALITY)}`);
|
str = PatcherUtils.insertAt(str, index, `&q=${getGlobalPref(GlobalPref.UI_IMAGE_QUALITY)}`);
|
||||||
return str;
|
return str;
|
||||||
}
|
},
|
||||||
};
|
|
||||||
|
injectHeaderUseEffect(str: string) {
|
||||||
|
let index = str.indexOf('className:"Header-module__header');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 300));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.header.rendered');
|
||||||
|
},
|
||||||
|
|
||||||
|
injectErrorPageUseEffect(str: string) {
|
||||||
|
let index = str.indexOf('"PureErrorPage-module__container');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, '})=>(0,', index, 200));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = PatcherUtils.insertAt(str, index + 4, '{');
|
||||||
|
str = PatcherUtils.injectUseEffect(str, index + 5, 'Script', 'ui.error.rendered');
|
||||||
|
str += '}';
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
injectStreamMenuUseEffect(str: string) {
|
||||||
|
let index = str.indexOf('"StreamMenu-module__container');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 200));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatcherUtils.injectUseEffect(str, index, 'Stream', 'ui.streamMenu.rendered');
|
||||||
|
},
|
||||||
|
|
||||||
|
injectGuideHomeUseEffect(str: string) {
|
||||||
|
let index = str.indexOf('"HomeLandingPage-module__authenticatedContentContainer');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 200));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.guideHome.rendered');
|
||||||
|
},
|
||||||
|
|
||||||
|
injectCreatePortal(str: string) {
|
||||||
|
let index = str.indexOf('.createPortal=function');
|
||||||
|
index > -1 && (index = PatcherUtils.indexOf(str, '{', index, 50, true));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = PatcherUtils.insertAt(str, index, codeCreatePortal);
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
injectAchievementsProgressUseEffect(str: string) {
|
||||||
|
let index = str.indexOf('"AchievementsButton-module__progressBarContainer');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 200));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.guideAchievementProgress.rendered');
|
||||||
|
},
|
||||||
|
|
||||||
|
injectAchievementsDetailUseEffect(str: string) {
|
||||||
|
let index = str.indexOf('GuideAchievementDetail.useParams()');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'const', index, 200));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
let index = str.indexOf('.ChildXboxTitleIds,offerings');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return{', index, 1000));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const varName = PatcherUtils.getVariableNameBefore(str, PatcherUtils.lastIndexOf(str, '=>{', index));
|
||||||
|
if (!varName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCode = `
|
||||||
|
const info = ${varName};
|
||||||
|
if (info.ProductTitle.includes('Xbox One')) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
str = PatcherUtils.insertAt(str, index, newCode);
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
} as const satisfies { [key: string]: PatchFunction };
|
||||||
|
|
||||||
let PATCH_ORDERS = PatcherUtils.filterPatches([
|
let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
...(AppInterface && getGlobalPref(GlobalPref.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
|
...(AppInterface && getGlobalPref(GlobalPref.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
|
||||||
'enableNativeMkb',
|
'enableNativeMkb',
|
||||||
'disableAbsoluteMouse',
|
'disableAbsoluteMouse',
|
||||||
] : []),
|
] : []) as PatchArray,
|
||||||
|
|
||||||
'exposeReactCreateComponent',
|
'exposeReactCreateComponent',
|
||||||
'gameCardCustomIcons',
|
|
||||||
// 'gameCardPassTitle',
|
|
||||||
|
|
||||||
...(getGlobalPref(GlobalPref.UI_IMAGE_QUALITY) < 90 ? [
|
'injectCreatePortal',
|
||||||
'setImageQuality',
|
|
||||||
] : []),
|
'broadcastPollingMode',
|
||||||
|
|
||||||
|
getGlobalPref(GlobalPref.UI_GAME_CARD_SHOW_WAIT_TIME) && 'patchSetCurrentFocus',
|
||||||
|
|
||||||
|
'patchGamepadPolling',
|
||||||
|
|
||||||
'modifyPreloadedState',
|
'modifyPreloadedState',
|
||||||
|
|
||||||
'optimizeGameSlugGenerator',
|
|
||||||
|
|
||||||
'detectBrowserRouterReady',
|
'detectBrowserRouterReady',
|
||||||
'patchRequestInfoCrash',
|
|
||||||
|
|
||||||
'disableStreamGate',
|
|
||||||
'broadcastPollingMode',
|
|
||||||
'patchGamepadPolling',
|
|
||||||
|
|
||||||
'exposeStreamSession',
|
'exposeStreamSession',
|
||||||
|
'supportLocalCoOp',
|
||||||
|
|
||||||
|
'disableStreamGate',
|
||||||
|
|
||||||
'exposeDialogRoutes',
|
'exposeDialogRoutes',
|
||||||
|
|
||||||
'homePageBeforeLoad',
|
...(getGlobalPref(GlobalPref.UI_IMAGE_QUALITY) < 90 ? [
|
||||||
'productDetailPageBeforeLoad',
|
'setImageQuality',
|
||||||
'streamPageBeforeLoad',
|
] : []) as PatchArray,
|
||||||
|
|
||||||
|
'patchRequestInfoCrash',
|
||||||
|
|
||||||
|
'injectErrorPageUseEffect',
|
||||||
|
|
||||||
|
'streamPageBeforeLoad',
|
||||||
|
'remotePlayStreamPageBeforeLoad',
|
||||||
|
|
||||||
|
'injectGuideHomeUseEffect',
|
||||||
|
'injectAchievementsProgressUseEffect',
|
||||||
|
'injectAchievementsDetailUseEffect',
|
||||||
'guideAchievementsDefaultLocked',
|
'guideAchievementsDefaultLocked',
|
||||||
|
|
||||||
|
'injectHeaderUseEffect',
|
||||||
|
|
||||||
|
'homePageBeforeLoad',
|
||||||
|
|
||||||
|
'patchCustomInputIcon',
|
||||||
|
|
||||||
|
'gameCardCustomIcons',
|
||||||
|
// 'gameCardPassTitle',
|
||||||
|
|
||||||
|
'productDetailPageBeforeLoad',
|
||||||
|
|
||||||
'enableTvRoutes',
|
'enableTvRoutes',
|
||||||
|
|
||||||
'supportLocalCoOp',
|
|
||||||
'overrideStorageGetSettings',
|
'overrideStorageGetSettings',
|
||||||
getGlobalPref(GlobalPref.UI_GAME_CARD_SHOW_WAIT_TIME) && 'patchSetCurrentFocus',
|
|
||||||
|
|
||||||
getGlobalPref(GlobalPref.UI_LAYOUT) !== UiLayout.DEFAULT && 'websiteLayout',
|
getGlobalPref(GlobalPref.UI_LAYOUT) !== UiLayout.DEFAULT && 'websiteLayout',
|
||||||
getGlobalPref(GlobalPref.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
|
getGlobalPref(GlobalPref.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
|
||||||
|
|
||||||
...(STATES.userAgent.capabilities.touch ? [
|
...(STATES.userAgent.capabilities.touch ? [
|
||||||
'disableTouchContextMenu',
|
'disableTouchContextMenu',
|
||||||
] : []),
|
] : []) as PatchArray,
|
||||||
|
|
||||||
...(getGlobalPref(GlobalPref.BLOCK_TRACKING) ? [
|
...(getGlobalPref(GlobalPref.BLOCK_TRACKING) ? [
|
||||||
'disableAiTrack',
|
'disableAiTrack',
|
||||||
'disableTelemetry',
|
// 'disableTelemetry',
|
||||||
|
|
||||||
'blockWebRtcStatsCollector',
|
'blockWebRtcStatsCollector',
|
||||||
'disableIndexDbLogging',
|
'disableIndexDbLogging',
|
||||||
|
|
||||||
'disableTelemetryProvider',
|
'disableTelemetryProvider',
|
||||||
] : []),
|
] : []) as PatchArray,
|
||||||
|
|
||||||
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [
|
...(!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY) ? [
|
||||||
'remotePlayKeepAlive',
|
'remotePlayKeepAlive',
|
||||||
'remotePlayDirectConnectUrl',
|
|
||||||
'remotePlayDisableAchievementToast',
|
'remotePlayDisableAchievementToast',
|
||||||
'remotePlayRecentlyUsedTitleIds',
|
|
||||||
'remotePlayWebTitle',
|
|
||||||
STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync',
|
STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync',
|
||||||
] : []),
|
] : []) as PatchArray,
|
||||||
|
|
||||||
...(BX_FLAGS.EnableXcloudLogging ? [
|
...(BX_FLAGS.EnableXcloudLogging ? [
|
||||||
'enableConsoleLogging',
|
'enableConsoleLogging',
|
||||||
'enableXcloudLogger',
|
'enableXcloudLogger',
|
||||||
] : []),
|
] : []) as PatchArray,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const hideSections = getGlobalPref(GlobalPref.UI_HIDE_SECTIONS);
|
const hideSections = getGlobalPref(GlobalPref.UI_HIDE_SECTIONS);
|
||||||
let HOME_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
let HOME_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
hideSections.includes(UiSection.NEWS) && 'ignoreNewsSection',
|
|
||||||
(getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.FRIENDS) || hideSections.includes(UiSection.FRIENDS)) && 'ignorePlayWithFriendsSection',
|
|
||||||
hideSections.includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
|
|
||||||
hideSections.includes(UiSection.GENRES) && 'ignoreGenresSection',
|
hideSections.includes(UiSection.GENRES) && 'ignoreGenresSection',
|
||||||
!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.BYOG) && hideSections.includes(UiSection.BOYG) && 'ignoreByogSection',
|
!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.BYOG) && hideSections.includes(UiSection.BOYG) && 'ignoreByogSection',
|
||||||
|
|
||||||
STATES.browser.capabilities.touch && hideSections.includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
|
STATES.browser.capabilities.touch && hideSections.includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
|
||||||
|
|
||||||
|
getGlobalPref(GlobalPref.UI_IMAGE_QUALITY) < 90 && 'setBackgroundImageQuality',
|
||||||
|
|
||||||
hideSections.some(value => [UiSection.NATIVE_MKB, UiSection.MOST_POPULAR].includes(value)) && 'ignoreSiglSections',
|
hideSections.some(value => [UiSection.NATIVE_MKB, UiSection.MOST_POPULAR].includes(value)) && 'ignoreSiglSections',
|
||||||
|
|
||||||
...(getGlobalPref(GlobalPref.UI_IMAGE_QUALITY) < 90 ? [
|
hideSections.includes(UiSection.NEWS) && 'ignoreNewsSection',
|
||||||
'setBackgroundImageQuality',
|
(getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.FRIENDS) || hideSections.includes(UiSection.FRIENDS)) && 'ignorePlayWithFriendsSection',
|
||||||
] : []),
|
hideSections.includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
|
||||||
|
|
||||||
...(blockSomeNotifications() ? [
|
...(blockSomeNotifications() ? [
|
||||||
'changeNotificationsSubscription',
|
'changeNotificationsSubscription',
|
||||||
] : []),
|
] : []) as PatchArray,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Only when playing
|
// Only when playing
|
||||||
@ -1236,6 +1353,8 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
|||||||
|
|
||||||
'alwaysShowStreamHud',
|
'alwaysShowStreamHud',
|
||||||
|
|
||||||
|
'injectStreamMenuUseEffect',
|
||||||
|
|
||||||
// 'exposeEventTarget',
|
// 'exposeEventTarget',
|
||||||
|
|
||||||
// Patch volume control for normal stream
|
// Patch volume control for normal stream
|
||||||
@ -1252,24 +1371,22 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
|||||||
(getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF || getGlobalPref(GlobalPref.TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
|
(getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF || getGlobalPref(GlobalPref.TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
|
||||||
getGlobalPref(GlobalPref.TOUCH_CONTROLLER_DEFAULT_OPACITY) !== 100 && 'patchTouchControlDefaultOpacity',
|
getGlobalPref(GlobalPref.TOUCH_CONTROLLER_DEFAULT_OPACITY) !== 100 && 'patchTouchControlDefaultOpacity',
|
||||||
(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,
|
||||||
|
|
||||||
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',
|
||||||
'patchRemotePlayMkb',
|
'patchRemotePlayMkb',
|
||||||
'remotePlayConnectMode',
|
] : []) as PatchArray,
|
||||||
] : []),
|
|
||||||
|
|
||||||
// Native MKB
|
// Native MKB
|
||||||
...(AppInterface && getGlobalPref(GlobalPref.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
|
...(AppInterface && getGlobalPref(GlobalPref.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
|
||||||
'patchMouseAndKeyboardEnabled',
|
'patchMouseAndKeyboardEnabled',
|
||||||
'disableNativeRequestPointerLock',
|
'disableNativeRequestPointerLock',
|
||||||
] : []),
|
] : []) as PatchArray,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let PRODUCT_DETAIL_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
let PRODUCT_DETAIL_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
@ -1282,6 +1399,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,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1364,6 +1482,7 @@ export class Patcher {
|
|||||||
let patchedFuncStr = funcStr;
|
let patchedFuncStr = funcStr;
|
||||||
|
|
||||||
let modified = false;
|
let modified = false;
|
||||||
|
const chunkAppliedPatches = [];
|
||||||
|
|
||||||
for (let patchIndex = 0; patchIndex < patchesToCheck.length; patchIndex++) {
|
for (let patchIndex = 0; patchIndex < patchesToCheck.length; patchIndex++) {
|
||||||
const patchName = patchesToCheck[patchIndex];
|
const patchName = patchesToCheck[patchIndex];
|
||||||
@ -1386,18 +1505,20 @@ export class Patcher {
|
|||||||
modified = true;
|
modified = true;
|
||||||
patchedFuncStr = tmpStr;
|
patchedFuncStr = tmpStr;
|
||||||
|
|
||||||
BxLogger.info(LOG_TAG, `✅ ${patchName}`);
|
|
||||||
appliedPatches.push(patchName);
|
appliedPatches.push(patchName);
|
||||||
|
chunkAppliedPatches.push(patchName);
|
||||||
|
|
||||||
// Remove patch
|
// Remove patch
|
||||||
patchesToCheck.splice(patchIndex, 1);
|
patchesToCheck.splice(patchIndex, 1);
|
||||||
patchIndex--;
|
patchIndex--;
|
||||||
PATCH_ORDERS = PATCH_ORDERS.filter(item => item != patchName);
|
PATCH_ORDERS = PATCH_ORDERS.filter(item => item != patchName);
|
||||||
BxLogger.info(LOG_TAG, 'Remaining patches', PATCH_ORDERS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply patched functions
|
// Apply patched functions
|
||||||
if (modified) {
|
if (modified) {
|
||||||
|
BxLogger.info(LOG_TAG, `✅ [${chunkId}] ${chunkAppliedPatches.join(', ')}`);
|
||||||
|
PATCH_ORDERS.length && BxLogger.info(LOG_TAG, 'Remaining patches', PATCH_ORDERS);
|
||||||
|
|
||||||
BX_FLAGS.Debug && console.time(LOG_TAG);
|
BX_FLAGS.Debug && console.time(LOG_TAG);
|
||||||
try {
|
try {
|
||||||
chunkData[chunkId] = eval(patchedFuncStr);
|
chunkData[chunkId] = eval(patchedFuncStr);
|
||||||
@ -1442,7 +1563,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');
|
||||||
@ -1461,24 +1584,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1492,7 +1616,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());
|
||||||
|
13
src/modules/patcher/patches/src/create-portal.ts
Normal file
13
src/modules/patcher/patches/src/create-portal.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
declare const arguments: any;
|
||||||
|
|
||||||
|
const $dom = arguments[1];
|
||||||
|
if ($dom && $dom instanceof HTMLElement && $dom.id === 'gamepass-dialog-root') {
|
||||||
|
let showing = false;
|
||||||
|
const $child = $dom.firstElementChild;
|
||||||
|
const $dialog = $child?.firstElementChild;
|
||||||
|
if ($dialog) {
|
||||||
|
showing = !$dialog.className.includes('pageChangeExit');
|
||||||
|
}
|
||||||
|
window.BxEventBus.Script.emit(showing ? 'dialog.shown' : 'dialog.dismissed', {});
|
||||||
|
}
|
@ -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();
|
||||||
|
@ -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('/launch/')) {
|
if (msg.reason === 'WarningForBeingIdle' && window.location.pathname.includes('/play/consoles/launch/')) {
|
||||||
$this$.sendKeepAlive();
|
$this$.sendKeepAlive();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return;
|
return;
|
||||||
|
13
src/modules/patcher/patches/src/stream-hud.ts
Normal file
13
src/modules/patcher/patches/src/stream-hud.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
declare let $guideUI$: any;
|
||||||
|
declare const $onShowStreamMenu$: any;
|
||||||
|
declare const $offset$: any;
|
||||||
|
|
||||||
|
// Expose onShowStreamMenu
|
||||||
|
window.BX_EXPOSED.showStreamMenu = $onShowStreamMenu$;
|
||||||
|
// Restore the "..." button
|
||||||
|
$guideUI$ = null;
|
||||||
|
|
||||||
|
window.BX_EXPOSED.reactUseEffect(() => {
|
||||||
|
window.BxEventBus.Stream.emit('ui.streamHud.rendered', { expanded: $offset$.x === 0 });
|
||||||
|
});
|
@ -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;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import vertClarityBoost from "./shaders/clarity-boost.vert" with { type: "text" };
|
import { compressCodeFile } from "@macros/build" with { type: "macro" };
|
||||||
import fsClarityBoost from "./shaders/clarity-boost.fs" with { type: "text" };
|
|
||||||
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);
|
||||||
@ -53,11 +54,11 @@ export class WebGL2Player extends BaseCanvasPlayer {
|
|||||||
|
|
||||||
// Vertex shader: Identity map
|
// Vertex shader: Identity map
|
||||||
const vShader = gl.createShader(gl.VERTEX_SHADER)!;
|
const vShader = gl.createShader(gl.VERTEX_SHADER)!;
|
||||||
gl.shaderSource(vShader, vertClarityBoost);
|
gl.shaderSource(vShader, compressCodeFile('./src/modules/player/webgl2/shaders/clarity-boost.vert') as any as string);
|
||||||
gl.compileShader(vShader);
|
gl.compileShader(vShader);
|
||||||
|
|
||||||
const fShader = gl.createShader(gl.FRAGMENT_SHADER)!;
|
const fShader = gl.createShader(gl.FRAGMENT_SHADER)!;
|
||||||
gl.shaderSource(fShader, fsClarityBoost);
|
gl.shaderSource(fShader, compressCodeFile('./src/modules/player/webgl2/shaders/clarity-boost.fs') as any as string);
|
||||||
gl.compileShader(fShader);
|
gl.compileShader(fShader);
|
||||||
|
|
||||||
// Create and link program
|
// Create and link program
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import wgslClarityBoost from "./shaders/clarity-boost.wgsl" with { type: "text" };
|
import { compressCodeFile } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { BaseCanvasPlayer } from "../base-canvas-player";
|
import { BaseCanvasPlayer } from "../base-canvas-player";
|
||||||
import { StreamPlayerType } from "@/enums/pref-values";
|
import { StreamPlayerType } from "@/enums/pref-values";
|
||||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
@ -70,7 +71,7 @@ export class WebGPUPlayer extends BaseCanvasPlayer {
|
|||||||
]);
|
]);
|
||||||
this.vertexBuffer.unmap();
|
this.vertexBuffer.unmap();
|
||||||
|
|
||||||
const shaderModule = WebGPUPlayer.device.createShaderModule({ code: wgslClarityBoost });
|
const shaderModule = WebGPUPlayer.device.createShaderModule({ code: compressCodeFile('./src/modules/player/webgpu/shaders/clarity-boost.wgsl') as any as string });
|
||||||
this.pipeline = WebGPUPlayer.device.createRenderPipeline({
|
this.pipeline = WebGPUPlayer.device.createRenderPipeline({
|
||||||
layout: 'auto',
|
layout: 'auto',
|
||||||
vertex: {
|
vertex: {
|
||||||
|
@ -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,12 +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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePopup(force = null) {
|
togglePopup(force = null) {
|
||||||
@ -220,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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
@ -120,8 +124,9 @@ export class SettingsManager {
|
|||||||
[StreamPref.STATS_QUICK_GLANCE_ENABLED]: {
|
[StreamPref.STATS_QUICK_GLANCE_ENABLED]: {
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
const value = getStreamPref(StreamPref.STATS_QUICK_GLANCE_ENABLED);
|
const value = getStreamPref(StreamPref.STATS_QUICK_GLANCE_ENABLED);
|
||||||
const streamStats = StreamStats.getInstance();
|
if (!value) {
|
||||||
value ? streamStats.quickGlanceSetup() : streamStats.quickGlanceStop();
|
StreamStats.getInstance().stop(true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[StreamPref.STATS_POSITION]: {
|
[StreamPref.STATS_POSITION]: {
|
||||||
|
@ -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),
|
||||||
|
@ -14,6 +14,7 @@ export class StreamStats {
|
|||||||
public static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats());
|
public static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats());
|
||||||
private readonly LOG_TAG = 'StreamStats';
|
private readonly LOG_TAG = 'StreamStats';
|
||||||
|
|
||||||
|
private isRunning = false;
|
||||||
private intervalId?: number | null;
|
private intervalId?: number | null;
|
||||||
private readonly REFRESH_INTERVAL = 1 * 1000;
|
private readonly REFRESH_INTERVAL = 1 * 1000;
|
||||||
|
|
||||||
@ -69,19 +70,23 @@ export class StreamStats {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private $container!: HTMLElement;
|
private $container!: HTMLElement;
|
||||||
|
private boundOnStreamHudStateChanged: typeof this.onStreamHudStateChanged;
|
||||||
quickGlanceObserver?: MutationObserver | null;
|
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
BxLogger.info(this.LOG_TAG, 'constructor()');
|
BxLogger.info(this.LOG_TAG, 'constructor()');
|
||||||
|
|
||||||
|
this.boundOnStreamHudStateChanged = this.onStreamHudStateChanged.bind(this);
|
||||||
|
BxEventBus.Stream.on('ui.streamHud.rendered', this.boundOnStreamHudStateChanged);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(glancing=false) {
|
async start(glancing=false) {
|
||||||
if (!this.isHidden() || (glancing && this.isGlancing())) {
|
if (this.isRunning || !this.isHidden() || (glancing && this.isGlancing())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isRunning = true;
|
||||||
this.intervalId && clearInterval(this.intervalId);
|
this.intervalId && clearInterval(this.intervalId);
|
||||||
await this.update(true);
|
await this.update(true);
|
||||||
|
|
||||||
@ -96,6 +101,7 @@ export class StreamStats {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isRunning = false;
|
||||||
this.intervalId && clearInterval(this.intervalId);
|
this.intervalId && clearInterval(this.intervalId);
|
||||||
this.intervalId = null;
|
this.intervalId = null;
|
||||||
|
|
||||||
@ -113,49 +119,22 @@ export class StreamStats {
|
|||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.stop();
|
this.stop();
|
||||||
this.quickGlanceStop();
|
|
||||||
this.hideSettingsUi();
|
this.hideSettingsUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
isHidden = () => this.$container.classList.contains('bx-gone');
|
isHidden = () => this.$container.classList.contains('bx-gone');
|
||||||
isGlancing = () => this.$container.dataset.display === 'glancing';
|
isGlancing = () => this.$container.dataset.display === 'glancing';
|
||||||
|
|
||||||
quickGlanceSetup() {
|
onStreamHudStateChanged({ expanded }: { expanded: boolean }) {
|
||||||
if (!STATES.isPlaying || this.quickGlanceObserver) {
|
if (!getStreamPref(StreamPref.STATS_QUICK_GLANCE_ENABLED)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const $uiContainer = document.querySelector('div[data-testid=ui-container]')!;
|
if (expanded) {
|
||||||
if (!$uiContainer) {
|
this.isHidden() && this.start(true);
|
||||||
return;
|
} else {
|
||||||
|
this.stop(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.quickGlanceObserver = new MutationObserver((mutationList, observer) => {
|
|
||||||
for (const record of mutationList) {
|
|
||||||
const $target = record.target as HTMLElement;
|
|
||||||
if (!$target.className || !$target.className.startsWith('GripHandle')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const expanded = (record.target as HTMLElement).ariaExpanded;
|
|
||||||
if (expanded === 'true') {
|
|
||||||
this.isHidden() && this.start(true);
|
|
||||||
} else {
|
|
||||||
this.stop(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.quickGlanceObserver.observe($uiContainer, {
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: ['aria-expanded'],
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
quickGlanceStop() {
|
|
||||||
this.quickGlanceObserver && this.quickGlanceObserver.disconnect();
|
|
||||||
this.quickGlanceObserver = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private update = async (forceUpdate=false) => {
|
private update = async (forceUpdate=false) => {
|
||||||
@ -249,7 +228,6 @@ export class StreamStats {
|
|||||||
if (PREF_STATS_SHOW_WHEN_PLAYING) {
|
if (PREF_STATS_SHOW_WHEN_PLAYING) {
|
||||||
streamStats.start();
|
streamStats.start();
|
||||||
} else if (PREF_STATS_QUICK_GLANCE) {
|
} else if (PREF_STATS_QUICK_GLANCE) {
|
||||||
streamStats.quickGlanceSetup();
|
|
||||||
// Show stats bar
|
// Show stats bar
|
||||||
!PREF_STATS_SHOW_WHEN_PLAYING && streamStats.start(true);
|
!PREF_STATS_SHOW_WHEN_PLAYING && streamStats.start(true);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import { t } from "@utils/translation.ts";
|
|||||||
import { StreamBadges } from "./stream-badges.ts";
|
import { StreamBadges } from "./stream-badges.ts";
|
||||||
import { StreamStats } from "./stream-stats.ts";
|
import { StreamStats } from "./stream-stats.ts";
|
||||||
import { SettingsDialog } from "../ui/dialog/settings-dialog.ts";
|
import { SettingsDialog } from "../ui/dialog/settings-dialog.ts";
|
||||||
import { BxEventBus } from "@/utils/bx-event-bus.ts";
|
|
||||||
|
|
||||||
|
|
||||||
export class StreamUiHandler {
|
export class StreamUiHandler {
|
||||||
@ -13,7 +12,6 @@ export class StreamUiHandler {
|
|||||||
private static $btnStreamStats: HTMLElement | null | undefined;
|
private static $btnStreamStats: HTMLElement | null | undefined;
|
||||||
private static $btnRefresh: HTMLElement | null | undefined;
|
private static $btnRefresh: HTMLElement | null | undefined;
|
||||||
private static $btnHome: HTMLElement | null | undefined;
|
private static $btnHome: HTMLElement | null | undefined;
|
||||||
private static observer: MutationObserver | undefined;
|
|
||||||
|
|
||||||
private static cloneStreamHudButton($btnOrg: HTMLElement, label: string, svgIcon: BxIconRaw): HTMLElement | null {
|
private static cloneStreamHudButton($btnOrg: HTMLElement, label: string, svgIcon: BxIconRaw): HTMLElement | null {
|
||||||
if (!$btnOrg) {
|
if (!$btnOrg) {
|
||||||
@ -101,7 +99,7 @@ export class StreamUiHandler {
|
|||||||
return $btn;
|
return $btn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async handleStreamMenu() {
|
static async handleStreamMenu() {
|
||||||
const $btnCloseHud = document.querySelector<HTMLElement>('button[class*=StreamMenu-module__backButton]');
|
const $btnCloseHud = document.querySelector<HTMLElement>('button[class*=StreamMenu-module__backButton]');
|
||||||
if (!$btnCloseHud) {
|
if (!$btnCloseHud) {
|
||||||
return;
|
return;
|
||||||
@ -134,13 +132,17 @@ export class StreamUiHandler {
|
|||||||
$menu?.appendChild(await StreamBadges.getInstance().render());
|
$menu?.appendChild(await StreamBadges.getInstance().render());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static handleSystemMenu($streamHud: HTMLElement) {
|
static handleSystemMenu($streamHud: HTMLElement) {
|
||||||
// Get the last button
|
// Get the last button
|
||||||
const $orgButton = $streamHud.querySelector<HTMLElement>('div[class^=HUDButton]');
|
const $orgButton = $streamHud.querySelector<HTMLElement>('div[class^=HUDButton]');
|
||||||
if (!$orgButton) {
|
if (!$orgButton) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StreamUiHandler.$btnStreamSettings && $streamHud.contains(StreamUiHandler.$btnStreamSettings)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const hideGripHandle = () => {
|
const hideGripHandle = () => {
|
||||||
// Grip handle
|
// Grip handle
|
||||||
const $gripHandle = document.querySelector<HTMLElement>('#StreamHud button[class^=GripHandle]');
|
const $gripHandle = document.querySelector<HTMLElement>('#StreamHud button[class^=GripHandle]');
|
||||||
@ -207,67 +209,5 @@ export class StreamUiHandler {
|
|||||||
StreamUiHandler.$btnStreamStats = undefined;
|
StreamUiHandler.$btnStreamStats = undefined;
|
||||||
StreamUiHandler.$btnRefresh = undefined;
|
StreamUiHandler.$btnRefresh = undefined;
|
||||||
StreamUiHandler.$btnHome = undefined;
|
StreamUiHandler.$btnHome = undefined;
|
||||||
|
|
||||||
StreamUiHandler.observer && StreamUiHandler.observer.disconnect();
|
|
||||||
StreamUiHandler.observer = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
static observe() {
|
|
||||||
StreamUiHandler.reset();
|
|
||||||
|
|
||||||
const $screen = document.querySelector('#PageContent section[class*=PureScreens]');
|
|
||||||
if (!$screen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const observer = new MutationObserver(mutationList => {
|
|
||||||
let item: MutationRecord;
|
|
||||||
for (item of mutationList) {
|
|
||||||
if (item.type !== 'childList') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.addedNodes.forEach(async $node => {
|
|
||||||
if (!$node || $node.nodeType !== Node.ELEMENT_NODE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let $elm: HTMLElement | null = $node as HTMLElement;
|
|
||||||
|
|
||||||
// Ignore non-HTML elements
|
|
||||||
if (!($elm instanceof HTMLElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const className = $elm.className || '';
|
|
||||||
|
|
||||||
// Error Page: .PureErrorPage.ErrorScreen
|
|
||||||
if (className.includes('PureErrorPage')) {
|
|
||||||
BxEventBus.Stream.emit('state.error', {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render badges
|
|
||||||
if (className.startsWith('StreamMenu-module__container')) {
|
|
||||||
StreamUiHandler.handleStreamMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (className.startsWith('Overlay-module_') || className.startsWith('InProgressScreen')) {
|
|
||||||
$elm = $elm.querySelector('#StreamHud');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$elm || ($elm.id || '') !== 'StreamHud') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle System Menu bar
|
|
||||||
StreamUiHandler.handleSystemMenu($elm);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe($screen, { subtree: true, childList: true });
|
|
||||||
StreamUiHandler.observer = observer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ export class TouchController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!layoutId) {
|
if (!layoutId) {
|
||||||
BxLogger.error(LOG_TAG, 'Invalid layoutId, show default controller');
|
BxLogger.warning(LOG_TAG, 'Invalid layoutId, show default controller');
|
||||||
TouchController.#enabled && TouchController.#showDefault();
|
TouchController.#enabled && TouchController.#showDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -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) => {
|
||||||
|
@ -44,14 +44,14 @@ export class RemotePlayDialog extends NavigationDialog {
|
|||||||
let $resolutions : HTMLSelectElement | NavigationElement = CE('select', false,
|
let $resolutions : HTMLSelectElement | NavigationElement = CE('select', false,
|
||||||
CE('option', { value: StreamResolution.DIM_720P }, '720p'),
|
CE('option', { value: StreamResolution.DIM_720P }, '720p'),
|
||||||
CE('option', { value: StreamResolution.DIM_1080P }, '1080p'),
|
CE('option', { value: StreamResolution.DIM_1080P }, '1080p'),
|
||||||
// CE('option', { value: StreamResolution.DIM_1080P_HQ }, `1080p (HQ)`),
|
CE('option', { value: StreamResolution.DIM_1080P_HQ }, `1080p (HQ)`),
|
||||||
);
|
);
|
||||||
|
|
||||||
$resolutions = BxSelectElement.create($resolutions as HTMLSelectElement);
|
$resolutions = BxSelectElement.create($resolutions as HTMLSelectElement);
|
||||||
$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 === '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');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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,
|
||||||
@ -949,6 +949,11 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
}
|
}
|
||||||
$row.dataset.type = settingTabContent.group;
|
$row.dataset.type = settingTabContent.group;
|
||||||
|
|
||||||
|
// Highlight "Bypass region" row
|
||||||
|
if (!STATES.supportedRegion && setting.pref === GlobalPref.SERVER_BYPASS_RESTRICTION) {
|
||||||
|
$row.classList.add('bx-settings-important-row');
|
||||||
|
}
|
||||||
|
|
||||||
$tabContent.appendChild($row);
|
$tabContent.appendChild($row);
|
||||||
!prefDefinition?.unsupported && setting.onCreated && setting.onCreated(setting, $control);
|
!prefDefinition?.unsupported && setting.onCreated && setting.onCreated(setting, $control);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
|
||||||
|
|
||||||
import { BxEvent } from "@/utils/bx-event";
|
|
||||||
import { AppInterface, STATES } from "@/utils/global";
|
import { AppInterface, STATES } from "@/utils/global";
|
||||||
import { createButton, ButtonStyle, CE } from "@/utils/html";
|
import { createButton, ButtonStyle, CE } from "@/utils/html";
|
||||||
import { t } from "@/utils/translation";
|
import { t } from "@/utils/translation";
|
||||||
import { SettingsDialog } from "./dialog/settings-dialog";
|
import { SettingsDialog } from "./dialog/settings-dialog";
|
||||||
import { TrueAchievements } from "@/utils/true-achievements";
|
|
||||||
import { BxIcon } from "@/utils/bx-icon";
|
import { BxIcon } from "@/utils/bx-icon";
|
||||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
import { getGlobalPref } from "@/utils/pref-utils";
|
import { getGlobalPref } from "@/utils/pref-utils";
|
||||||
@ -141,11 +137,9 @@ export class GuideMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
injectHome($root: HTMLElement, isPlaying = false) {
|
injectHome($root: HTMLElement, isPlaying = false) {
|
||||||
if (isFullVersion()) {
|
const $buttons = this.renderButtons();
|
||||||
const $achievementsProgress = $root.querySelector('button[class*=AchievementsButton-module__progressBarContainer]');
|
if ($root.contains($buttons)) {
|
||||||
if ($achievementsProgress) {
|
return;
|
||||||
TrueAchievements.getInstance().injectAchievementsProgress($achievementsProgress as HTMLElement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the element to add buttons to
|
// Find the element to add buttons to
|
||||||
@ -169,67 +163,7 @@ export class GuideMenu {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const $buttons = this.renderButtons();
|
|
||||||
$buttons.dataset.isPlaying = isPlaying.toString();
|
$buttons.dataset.isPlaying = isPlaying.toString();
|
||||||
$target.insertAdjacentElement('afterend', $buttons);
|
$target.insertAdjacentElement('afterend', $buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onShown = async (e: Event) => {
|
|
||||||
const where = (e as any).where as GuideMenuTab;
|
|
||||||
|
|
||||||
if (where === GuideMenuTab.HOME) {
|
|
||||||
const $root = document.querySelector<HTMLElement>('#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]');
|
|
||||||
$root && this.injectHome($root, STATES.isPlaying);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addEventListeners() {
|
|
||||||
window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, this.onShown);
|
|
||||||
}
|
|
||||||
|
|
||||||
observe($addedElm: HTMLElement) {
|
|
||||||
let className = $addedElm.className;
|
|
||||||
|
|
||||||
// Fix custom buttons disappearing in Guide Menu (#551)
|
|
||||||
if (!className) {
|
|
||||||
className = $addedElm.firstElementChild?.className ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!className || className.startsWith('bx-')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrueAchievements
|
|
||||||
if (isFullVersion() && className.includes('AchievementsButton-module__progressBarContainer')) {
|
|
||||||
TrueAchievements.getInstance().injectAchievementsProgress($addedElm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!className.startsWith('NavigationAnimation') &&
|
|
||||||
!className.startsWith('DialogRoutes') &&
|
|
||||||
!className.startsWith('Dialog-module__container')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Achievement Details page
|
|
||||||
if (isFullVersion()) {
|
|
||||||
const $achievDetailPage = $addedElm.querySelector('div[class*=AchievementDetailPage]');
|
|
||||||
if ($achievDetailPage) {
|
|
||||||
TrueAchievements.getInstance().injectAchievementDetailPage($achievDetailPage as HTMLElement);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find navigation bar
|
|
||||||
const $selectedTab = $addedElm.querySelector('div[class^=NavigationMenu] button[aria-selected=true');
|
|
||||||
if ($selectedTab) {
|
|
||||||
let $elm: Element | null = $selectedTab;
|
|
||||||
let index;
|
|
||||||
for (index = 0; ($elm = $elm?.previousElementSibling); index++);
|
|
||||||
|
|
||||||
if (index === 0) {
|
|
||||||
BxEvent.dispatch(window, BxEvent.XCLOUD_GUIDE_MENU_SHOWN, { where: GuideMenuTab.HOME });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { SCRIPT_VERSION } from "@utils/global";
|
import { SCRIPT_VERSION, STATES } from "@utils/global";
|
||||||
import { createButton, ButtonStyle, CE, isElementVisible } from "@utils/html";
|
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||||
import { BxIcon } from "@utils/bx-icon";
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
import { getPreferredServerRegion } from "@utils/region";
|
import { getPreferredServerRegion } from "@utils/region";
|
||||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||||
@ -10,6 +10,8 @@ import { SettingsDialog } from "./dialog/settings-dialog";
|
|||||||
import { GlobalPref } from "@/enums/pref-keys";
|
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 { BlockFeature } from "@/enums/pref-values";
|
||||||
|
|
||||||
export class HeaderSection {
|
export class HeaderSection {
|
||||||
private static instance: HeaderSection;
|
private static instance: HeaderSection;
|
||||||
@ -20,9 +22,6 @@ export class HeaderSection {
|
|||||||
private $btnSettings: HTMLElement;
|
private $btnSettings: HTMLElement;
|
||||||
private $buttonsWrapper: HTMLElement;
|
private $buttonsWrapper: HTMLElement;
|
||||||
|
|
||||||
private observer?: MutationObserver;
|
|
||||||
private timeoutId?: number | null;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
BxLogger.info(this.LOG_TAG, 'constructor()');
|
BxLogger.info(this.LOG_TAG, 'constructor()');
|
||||||
|
|
||||||
@ -38,76 +37,63 @@ export class HeaderSection {
|
|||||||
this.$btnRemotePlay = null;
|
this.$btnRemotePlay = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$btnSettings = createButton({
|
let $btnSettings = this.$btnSettings = createButton({
|
||||||
classes: ['bx-header-settings-button'],
|
classes: ['bx-header-settings-button', 'bx-gone'],
|
||||||
label: '???',
|
label: t('better-xcloud'),
|
||||||
style: ButtonStyle.FROSTED | ButtonStyle.DROP_SHADOW | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_HEIGHT,
|
style: ButtonStyle.FROSTED | ButtonStyle.DROP_SHADOW | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_HEIGHT,
|
||||||
onClick: e => SettingsDialog.getInstance().show(),
|
onClick: e => SettingsDialog.getInstance().show(),
|
||||||
});
|
});
|
||||||
|
|
||||||
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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
BxEventBus.Script.on('xcloud.server', ({status}) => {
|
||||||
|
if (status === 'ready') {
|
||||||
|
STATES.isSignedIn = true;
|
||||||
|
|
||||||
|
// Show server name
|
||||||
|
$btnSettings.querySelector('span')!.textContent = getPreferredServerRegion(true) || t('better-xcloud');
|
||||||
|
const PREF_LATEST_VERSION = getGlobalPref(GlobalPref.VERSION_LATEST);
|
||||||
|
// Show new update status
|
||||||
|
if (!SCRIPT_VERSION.includes('beta') && PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) {
|
||||||
|
$btnSettings.setAttribute('data-update-available', 'true');
|
||||||
|
}
|
||||||
|
} else if (status === 'unavailable') {
|
||||||
|
STATES.supportedRegion = false;
|
||||||
|
|
||||||
|
// Open Settings dialog on Unsupported page
|
||||||
|
const $unsupportedPage = document.querySelector<HTMLElement>('div[class^=UnsupportedMarketPage-module__container]');
|
||||||
|
if ($unsupportedPage) {
|
||||||
|
SettingsDialog.getInstance().show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$btnSettings.classList.remove('bx-gone');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private injectSettingsButton($parent?: HTMLElement) {
|
checkHeader = () => {
|
||||||
if (!$parent) {
|
const $header = document.querySelector('#gamepass-root header[class^=Header-module__header]');
|
||||||
|
if (!$header) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PREF_LATEST_VERSION = getGlobalPref(GlobalPref.VERSION_LATEST);
|
let $target = $header.querySelector('div[class*=EdgewaterHeader-module__rightSectionSpacing], div[class*=RemotePlayHeader-module__rightSectionSpacing]');
|
||||||
|
|
||||||
// Setup Settings button
|
|
||||||
const $btnSettings = this.$btnSettings;
|
|
||||||
if (isElementVisible(this.$buttonsWrapper)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$btnSettings.querySelector('span')!.textContent = getPreferredServerRegion(true) || t('better-xcloud');
|
|
||||||
|
|
||||||
// Show new update status
|
|
||||||
if (!SCRIPT_VERSION.includes('beta') && PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) {
|
|
||||||
$btnSettings.setAttribute('data-update-available', 'true');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the Settings button to the web page
|
|
||||||
$parent.appendChild(this.$buttonsWrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkHeader = () => {
|
|
||||||
let $target = document.querySelector('#PageContent div[class*=EdgewaterHeader-module__rightSectionSpacing]');
|
|
||||||
if (!$target) {
|
if (!$target) {
|
||||||
$target = document.querySelector('div[class^=UnsupportedMarketPage-module__buttons]');
|
$target = document.querySelector('div[class^=UnsupportedMarketPage-module__buttons]');
|
||||||
}
|
}
|
||||||
|
|
||||||
$target && this.injectSettingsButton($target as HTMLElement);
|
// Add the Settings button to the web page
|
||||||
}
|
$target?.appendChild(this.$buttonsWrapper);
|
||||||
|
|
||||||
private watchHeader() {
|
if (!STATES.isSignedIn) {
|
||||||
const $root = document.querySelector('#PageContent header') || document.querySelector('#root');
|
BxEventBus.Script.emit('xcloud.server', { status: 'signed-out' });
|
||||||
if (!$root) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timeoutId && clearTimeout(this.timeoutId);
|
|
||||||
this.timeoutId = null;
|
|
||||||
|
|
||||||
this.observer && this.observer.disconnect();
|
|
||||||
this.observer = new MutationObserver(mutationList => {
|
|
||||||
this.timeoutId && clearTimeout(this.timeoutId);
|
|
||||||
this.timeoutId = window.setTimeout(this.checkHeader, 2000);
|
|
||||||
});
|
|
||||||
this.observer.observe($root, { subtree: true, childList: true });
|
|
||||||
|
|
||||||
this.checkHeader();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showRemotePlayButton() {
|
showRemotePlayButton() {
|
||||||
this.$btnRemotePlay?.classList.remove('bx-gone');
|
this.$btnRemotePlay?.classList.remove('bx-gone');
|
||||||
}
|
}
|
||||||
|
|
||||||
static watchHeader() {
|
|
||||||
HeaderSection.getInstance().watchHeader();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
1
src/types/global.d.ts
vendored
1
src/types/global.d.ts
vendored
@ -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'];
|
||||||
|
4
src/types/index.d.ts
vendored
4
src/types/index.d.ts
vendored
@ -36,7 +36,7 @@ type XcloudTitleInfo = {
|
|||||||
hasMkbSupport: boolean;
|
hasMkbSupport: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
product: {
|
productInfo: {
|
||||||
title: string;
|
title: string;
|
||||||
heroImageUrl: string;
|
heroImageUrl: string;
|
||||||
titledHeroImageUrl: string;
|
titledHeroImageUrl: string;
|
||||||
@ -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;
|
||||||
|
4
src/types/states.d.ts
vendored
4
src/types/states.d.ts
vendored
@ -41,11 +41,7 @@ type BxStates = {
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
remotePlay: Partial<{
|
remotePlay: Partial<{
|
||||||
isPlaying: boolean;
|
|
||||||
server: string;
|
server: string;
|
||||||
config: {
|
|
||||||
serverId: string;
|
|
||||||
};
|
|
||||||
titleId?: 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 = {
|
type StreamPlayerOptions = {
|
||||||
processing: StreamVideoProcessing,
|
processing: StreamVideoProcessing,
|
||||||
|
processingMode: StreamVideoProcessingMode,
|
||||||
sharpness: number,
|
sharpness: number,
|
||||||
saturation: number,
|
saturation: number,
|
||||||
contrast: number,
|
contrast: number,
|
||||||
|
@ -7,9 +7,10 @@ import type { SpeakerState } from "@/modules/shortcuts/sound-shortcut";
|
|||||||
|
|
||||||
type EventCallback<T = any> = (payload: T) => void;
|
type EventCallback<T = any> = (payload: T) => void;
|
||||||
|
|
||||||
type ScriptEvents = {
|
export type ScriptEvents = {
|
||||||
'xcloud.server.ready': {};
|
'xcloud.server': {
|
||||||
'xcloud.server.unavailable': {};
|
status: 'ready' | 'unavailable' | 'signed-out',
|
||||||
|
};
|
||||||
|
|
||||||
'dialog.shown': {};
|
'dialog.shown': {};
|
||||||
'dialog.dismissed': {};
|
'dialog.dismissed': {};
|
||||||
@ -34,14 +35,20 @@ type ScriptEvents = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
'webgpu.ready': {},
|
'webgpu.ready': {},
|
||||||
|
|
||||||
|
'ui.header.rendered': {},
|
||||||
|
'ui.error.rendered': {},
|
||||||
|
|
||||||
|
'ui.guideHome.rendered': {},
|
||||||
|
'ui.guideAchievementProgress.rendered': {},
|
||||||
|
'ui.guideAchievementDetail.rendered': {},
|
||||||
};
|
};
|
||||||
|
|
||||||
type StreamEvents = {
|
export type StreamEvents = {
|
||||||
'state.loading': {};
|
'state.loading': {};
|
||||||
'state.starting': {};
|
'state.starting': {};
|
||||||
'state.playing': { $video?: HTMLVideoElement };
|
'state.playing': { $video?: HTMLVideoElement };
|
||||||
'state.stopped': {};
|
'state.stopped': {};
|
||||||
'state.error': {};
|
|
||||||
|
|
||||||
'xboxTitleId.changed': {
|
'xboxTitleId.changed': {
|
||||||
id: number;
|
id: number;
|
||||||
@ -65,6 +72,9 @@ type StreamEvents = {
|
|||||||
// Inside patch
|
// Inside patch
|
||||||
'microphone.state.changed': { state: MicrophoneState };
|
'microphone.state.changed': { state: MicrophoneState };
|
||||||
|
|
||||||
|
'ui.streamHud.rendered': { expanded: boolean },
|
||||||
|
'ui.streamMenu.rendered': {},
|
||||||
|
|
||||||
dataChannelCreated: { dataChannel: RTCDataChannel };
|
dataChannelCreated: { dataChannel: RTCDataChannel };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ export namespace BxEvent {
|
|||||||
export const TOUCH_LAYOUT_MANAGER_READY = 'bx-touch-layout-manager-ready';
|
export const TOUCH_LAYOUT_MANAGER_READY = 'bx-touch-layout-manager-ready';
|
||||||
|
|
||||||
// Inside app
|
// Inside app
|
||||||
|
// TODO: Use EventBus
|
||||||
export const REMOTE_PLAY_READY = 'bx-remote-play-ready';
|
export const REMOTE_PLAY_READY = 'bx-remote-play-ready';
|
||||||
export const REMOTE_PLAY_FAILED = 'bx-remote-play-failed';
|
export const REMOTE_PLAY_FAILED = 'bx-remote-play-failed';
|
||||||
|
|
||||||
|
@ -242,6 +242,7 @@ export const BxExposed = {
|
|||||||
|
|
||||||
localCoOpManager: isFullVersion() ? LocalCoOpManager.getInstance() : null,
|
localCoOpManager: isFullVersion() ? LocalCoOpManager.getInstance() : null,
|
||||||
reactCreateElement: function(...args: any[]) {},
|
reactCreateElement: function(...args: any[]) {},
|
||||||
|
reactUseEffect: function(...args: any[]) {},
|
||||||
|
|
||||||
createReactLocalCoOpIcon: isFullVersion() ? (attrs: any): any => {
|
createReactLocalCoOpIcon: isFullVersion() ? (attrs: any): any => {
|
||||||
const reactCE = window.BX_EXPOSED.reactCreateElement;
|
const reactCE = window.BX_EXPOSED.reactCreateElement;
|
||||||
@ -260,4 +261,9 @@ export const BxExposed = {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} : () => {},
|
} : () => {},
|
||||||
|
|
||||||
|
hasCustomTouchControl: TouchController.hasCustomControl,
|
||||||
|
hasCustomNativeMkb: (productId: string) => {
|
||||||
|
return BX_FLAGS.ForceNativeMkbTitles?.includes(productId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -10,6 +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.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY),
|
||||||
|
EnableConsoles: !getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Enable Native Mouse & Keyboard
|
// Enable Native Mouse & Keyboard
|
||||||
|
@ -1,9 +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 { HeaderSection } from "@/modules/ui/header";
|
|
||||||
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";
|
||||||
|
|
||||||
@ -27,15 +23,9 @@ 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();
|
||||||
|
|
||||||
LoadingScreen.reset();
|
LoadingScreen.reset();
|
||||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
|
||||||
|
|
||||||
BxEventBus.Stream.emit('state.stopped', {});
|
BxEventBus.Stream.emit('state.stopped', {});
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
@ -55,9 +56,9 @@ export function patchVideoApi() {
|
|||||||
return nativePlay.apply(this);
|
return nativePlay.apply(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const $parent = this.parentElement!!;
|
const $parent = this.parentElement;
|
||||||
// Video tag is stream player
|
// Video tag is stream player
|
||||||
if (!this.src && $parent.dataset.testid === 'media-container') {
|
if (!this.src && $parent?.dataset.testid === 'media-container') {
|
||||||
this.addEventListener('loadedmetadata', showFunc, { once: true });
|
this.addEventListener('loadedmetadata', showFunc, { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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';
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
import { GuideMenu } from "@/modules/ui/guide-menu";
|
|
||||||
import { BX_FLAGS } from "./bx-flags";
|
|
||||||
import { BxLogger } from "./bx-logger";
|
|
||||||
import { BxIcon } from "./bx-icon";
|
|
||||||
import { AppInterface } from "./global";
|
|
||||||
import { createButton, ButtonStyle } from "./html";
|
|
||||||
import { t } from "./translation";
|
|
||||||
import { parseDetailsPath } from "./utils";
|
|
||||||
import { BxEventBus } from "./bx-event-bus";
|
|
||||||
|
|
||||||
|
|
||||||
export class RootDialogObserver {
|
|
||||||
private static $btnShortcut = AppInterface && createButton({
|
|
||||||
icon: BxIcon.CREATE_SHORTCUT,
|
|
||||||
label: t('create-shortcut'),
|
|
||||||
style: ButtonStyle.FOCUSABLE | ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.NORMAL_CASE | ButtonStyle.NORMAL_LINK,
|
|
||||||
onClick: e => {
|
|
||||||
window.BX_EXPOSED.dialogRoutes?.closeAll();
|
|
||||||
|
|
||||||
const $btn = (e.target as HTMLElement).closest('button');
|
|
||||||
AppInterface.createShortcut($btn?.dataset.path);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
private static $btnWallpaper = AppInterface && createButton({
|
|
||||||
icon: BxIcon.DOWNLOAD,
|
|
||||||
label: t('wallpaper'),
|
|
||||||
style: ButtonStyle.FOCUSABLE | ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.NORMAL_CASE | ButtonStyle.NORMAL_LINK,
|
|
||||||
onClick: e => {
|
|
||||||
window.BX_EXPOSED.dialogRoutes?.closeAll();
|
|
||||||
|
|
||||||
const $btn = (e.target as HTMLElement).closest('button');
|
|
||||||
const details = parseDetailsPath($btn!.dataset.path!);
|
|
||||||
details && AppInterface.downloadWallpapers(details.titleSlug, details.productId);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
private static handleGameCardMenu($root: HTMLElement) {
|
|
||||||
const $detail = $root.querySelector('a[href^="/play/"]') as HTMLAnchorElement;
|
|
||||||
if (!$detail) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = $detail.getAttribute('href')!;
|
|
||||||
RootDialogObserver.$btnShortcut.dataset.path = path;
|
|
||||||
RootDialogObserver.$btnWallpaper.dataset.path = path;
|
|
||||||
|
|
||||||
$root.append(RootDialogObserver.$btnShortcut, RootDialogObserver.$btnWallpaper);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static handleAddedElement($root: HTMLElement, $addedElm: HTMLElement): boolean {
|
|
||||||
if (AppInterface && $addedElm.className.startsWith('SlideSheet-module__container')) {
|
|
||||||
// Game card's context menu
|
|
||||||
const $gameCardMenu = $addedElm.querySelector<HTMLElement>('div[class^=MruContextMenu],div[class^=GameCardContextMenu]');
|
|
||||||
if ($gameCardMenu) {
|
|
||||||
RootDialogObserver.handleGameCardMenu($gameCardMenu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if ($root.querySelector('div[class*=GuideDialog]')) {
|
|
||||||
// Guide menu
|
|
||||||
GuideMenu.getInstance().observe($addedElm);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static observe($root: HTMLElement) {
|
|
||||||
let beingShown = false;
|
|
||||||
|
|
||||||
const observer = new MutationObserver(mutationList => {
|
|
||||||
for (const mutation of mutationList) {
|
|
||||||
if (mutation.type !== 'childList') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BX_FLAGS.Debug && BxLogger.warning('RootDialog', 'added', mutation.addedNodes);
|
|
||||||
if (mutation.addedNodes.length === 1) {
|
|
||||||
const $addedElm = mutation.addedNodes[0];
|
|
||||||
if ($addedElm instanceof HTMLElement) {
|
|
||||||
RootDialogObserver.handleAddedElement($root, $addedElm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
|
||||||
if (shown !== beingShown) {
|
|
||||||
beingShown = shown;
|
|
||||||
BxEventBus.Script.emit(shown ? 'dialog.shown' : 'dialog.dismissed', {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
observer.observe($root, { subtree: true, childList: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
public static waitForRootDialog() {
|
|
||||||
const observer = new MutationObserver(mutationList => {
|
|
||||||
for (const mutation of mutationList) {
|
|
||||||
if (mutation.type !== 'childList') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $target = mutation.target as HTMLElement;
|
|
||||||
if ($target.id && $target.id === 'gamepass-dialog-root') {
|
|
||||||
observer.disconnect();
|
|
||||||
RootDialogObserver.observe($target);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
observer.observe(document.documentElement, { subtree: true, childList: true });
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
||||||
|
@ -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";
|
||||||
@ -14,7 +14,6 @@ import { ControllerCustomizationDefaultPresetId } from "../local-db/controller-c
|
|||||||
import { ControllerShortcutDefaultId } from "../local-db/controller-shortcuts-table";
|
import { ControllerShortcutDefaultId } from "../local-db/controller-shortcuts-table";
|
||||||
import { BxEventBus } from "../bx-event-bus";
|
import { BxEventBus } from "../bx-event-bus";
|
||||||
import { WebGPUPlayer } from "@/modules/player/webgpu/webgpu-player";
|
import { WebGPUPlayer } from "@/modules/player/webgpu/webgpu-player";
|
||||||
import { BX_FLAGS } from "../bx-flags";
|
|
||||||
|
|
||||||
|
|
||||||
export class StreamSettingsStorage extends BaseSettingsStorage<StreamPref> {
|
export class StreamSettingsStorage extends BaseSettingsStorage<StreamPref> {
|
||||||
@ -180,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,
|
||||||
@ -229,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',
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
|
@ -52,7 +52,7 @@ export class XcloudInterceptor {
|
|||||||
const response = await NATIVE_FETCH(request, init);
|
const response = await NATIVE_FETCH(request, init);
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
// Unsupported region
|
// Unsupported region
|
||||||
BxEventBus.Script.emit('xcloud.server.unavailable', {});
|
BxEventBus.Script.emit('xcloud.server', { status: 'unavailable' });
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,8 +92,6 @@ export class XcloudInterceptor {
|
|||||||
STATES.serverRegions[region.name] = Object.assign({}, region);
|
STATES.serverRegions[region.name] = Object.assign({}, region);
|
||||||
}
|
}
|
||||||
|
|
||||||
BxEventBus.Script.emit('xcloud.server.ready', {});
|
|
||||||
|
|
||||||
const preferredRegion = getPreferredServerRegion();
|
const preferredRegion = getPreferredServerRegion();
|
||||||
if (preferredRegion && preferredRegion in STATES.serverRegions) {
|
if (preferredRegion && preferredRegion in STATES.serverRegions) {
|
||||||
const tmp = Object.assign({}, STATES.serverRegions[preferredRegion]);
|
const tmp = Object.assign({}, STATES.serverRegions[preferredRegion]);
|
||||||
@ -104,6 +102,7 @@ export class XcloudInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
STATES.gsToken = obj.gsToken;
|
STATES.gsToken = obj.gsToken;
|
||||||
|
BxEventBus.Script.emit('xcloud.server', { status: 'ready' });
|
||||||
|
|
||||||
response.json = () => Promise.resolve(obj);
|
response.json = () => Promise.resolve(obj);
|
||||||
return response;
|
return response;
|
||||||
|
@ -95,7 +95,7 @@ export class XhomeInterceptor {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
TouchController.enable();
|
TouchController.enable();
|
||||||
TouchController.requestCustomLayouts(xboxTitleId);
|
TouchController.requestCustomLayouts();
|
||||||
}
|
}
|
||||||
|
|
||||||
response.json = () => Promise.resolve(obj);
|
response.json = () => Promise.resolve(obj);
|
||||||
|
Reference in New Issue
Block a user