Compare commits

...

36 Commits

Author SHA1 Message Date
HorizonCode 4326199a56 chore: add theme hash checking and alerts 2026-05-08 18:03:26 +02:00
HorizonCode 7f65384b1c chore: add code signing to vscode config 2026-05-08 18:02:32 +02:00
HorizonCode aacd0d1d91 chore: update readme 2026-05-08 18:02:16 +02:00
HorizonCode 079a076f99 chore: update launcher preview 2026-05-08 18:01:55 +02:00
HorizonCode 6d88c39ce2 chore: pause media when launcher is hidden 2026-05-03 17:50:06 +02:00
HorizonCode 56608a64fc chore: use folder name as identification 2026-05-03 15:24:31 +02:00
HorizonCode eaf6f5976d fix: always use underscore 2026-05-03 00:17:50 +02:00
HorizonCode 93f1f380ec fix: regex es6 issue 2026-05-03 00:16:26 +02:00
HorizonCode c47ea1f976 fix: toSafeName windows forbidden symbols 2026-05-03 00:09:01 +02:00
HorizonCode 09af78f1aa fix: use actual setted volume for fade 2026-05-02 22:21:28 +02:00
HorizonCode 3900b5c6cf chore: add volume slider
Co-authored-by: Copilot <copilot@github.com>
2026-05-02 22:15:39 +02:00
HorizonCode adf607e24c chore: clean up comments 2026-05-02 19:56:37 +02:00
HorizonCode 1128b51778 fix: multiple downloads 2026-05-02 19:55:49 +02:00
HorizonCode d435f0c09a chore: add theme uninstall and update 2026-05-02 02:19:00 +02:00
HorizonCode b188e2dee8 chore: remove bundled themes 2026-05-02 00:39:47 +02:00
HorizonCode 8fd56ae749 chore: add custom themes
Co-authored-by: Copilot <copilot@github.com>
2026-05-02 00:38:56 +02:00
HorizonCode b2e7d22d28 chore: add lost umbrella video 2026-04-29 23:33:51 +02:00
HorizonCode d871e06172 chore: add execution clap video 2026-04-29 23:33:15 +02:00
HorizonCode 6a1c5441c3 chore: remove konami code, add themes 2026-04-29 23:32:47 +02:00
HorizonCode ef36f49a6f fix: improve presence reconnection logic and error handling
Co-authored-by: Copilot <copilot@github.com>
2026-04-26 14:04:06 +02:00
HorizonCode 7e40536ec8 chore: add installerIcon to NSIS configuration 2026-04-26 11:29:31 +02:00
HorizonCode 694d403aeb chore: bump version 2026-04-26 11:19:53 +02:00
HorizonCode 9515892b2f fix: remove console log 2026-04-26 11:19:42 +02:00
HorizonCode 0a58f0f63b chore: update profile settings 2026-04-26 11:19:27 +02:00
HorizonCode 60e3ef4131 chore: add umami typing 2026-04-21 09:25:59 +02:00
HorizonCode 7375cccc05 chore: bump version 2026-04-20 12:16:26 +02:00
HorizonCode 251bf8177c chore: add all avalilable fields to beatmap struct 2026-04-20 08:22:40 +02:00
HorizonCode bf4ce18671 chore: replace osynic_osudb with own osudb parser 2026-04-19 18:43:22 +02:00
HorizonCode 4dc83c882d todo: check if this is still needed for linux 2026-04-18 02:34:16 +02:00
HorizonCode e20f11be64 chore: bump version 2026-04-18 02:34:00 +02:00
HorizonCode 407996269e fix: do not save config in setupwizard on value change 2026-04-18 02:33:43 +02:00
HorizonCode 95c6db6a1d fix: remove unused import, set firstStartup on top 2026-04-18 02:33:24 +02:00
HorizonCode a87a4bcc54 chore: bump version and update deps 2026-04-18 02:32:54 +02:00
HorizonCode 97f072f4ce refactor: improve error handling and code readability in replace_ui_files and check_for_corruption functions 2026-04-14 22:52:16 +02:00
HorizonCode 1aa3070ea1 refactor: remove PermissionDenied variant from ReplaceUIError enum 2026-04-14 22:37:33 +02:00
HorizonCode d3c3e4e771 refactor: replace symlink approach with a marker file approach 2026-04-14 22:36:26 +02:00
31 changed files with 1782 additions and 568 deletions
+1
View File
@@ -1,5 +1,6 @@
{
"svelte.enable-ts-plugin": true,
"git.enableCommitSigning": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
+7
View File
@@ -22,6 +22,7 @@ It enhances the osu! experience with quality-of-life features and integration sp
- [x] Displays ping to the EZPPFarm server
- [x] Discord Rich Presence
- [x] Performance Display Overlay (currently in experimental patcher release stream)
- [x] Launcher Theming
---
@@ -73,3 +74,9 @@ src-tauri/target/release/ezpplauncher.exe
├── package.json # Bun project config
└── README.md
```
---
## 🎨 Launcher Themes
By default, the launcher includes several downloadable themes. If you want to create your own custom theme, please use the [create-ezpplauncher-theme](https://git.ez-pp.farm/EZPPFarm/create-ezpplauncher-theme) repository for a template and instructions.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 157 KiB

+29 -7
View File
@@ -12,13 +12,13 @@
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.1.11",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-dialog": "^2.6.0",
"@tauri-apps/plugin-fs": "^2.4.5",
"@tauri-apps/plugin-dialog": "^2.7.0",
"@tauri-apps/plugin-fs": "^2.5.0",
"@tauri-apps/plugin-shell": "^2.3.5",
"@types/umami": "^2.10.1",
"animejs": "^4.1.3",
"buffer": "^6.0.3",
"crypto-js": "^4.2.0",
"jszip": "^3.10.1",
"lucide-svelte": "^1.0.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
@@ -284,9 +284,9 @@
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ=="],
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.6.0", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg=="],
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.7.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw=="],
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.5", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA=="],
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.5.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-c83kbz61AK+rKjhS+je9+stIO27nXj7p9cqeg36TwkIUtxpCFTttlHHtqon6h6FN54cXjyAjlMPOJcW3mwE5XQ=="],
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.5", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg=="],
@@ -312,8 +312,6 @@
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@types/umami": ["@types/umami@2.10.1", "", {}, "sha512-CvOEMq+BFOUXj8yciq4hPsdOt0o9KBg5jQXnF1FrdOMKZMxJiwryyZNLa9Ud2NVO6nXSwU22YMKR3E+Xhbo+DA=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.42.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/type-utils": "8.42.0", "@typescript-eslint/utils": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.42.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.42.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg=="],
@@ -392,6 +390,8 @@
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="],
@@ -486,12 +486,16 @@
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
"immutable": ["immutable@5.1.3", "", {}, "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
@@ -502,6 +506,8 @@
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
@@ -514,6 +520,8 @@
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
@@ -522,6 +530,8 @@
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
@@ -608,6 +618,8 @@
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
@@ -636,6 +648,8 @@
"prettier-plugin-svelte": ["prettier-plugin-svelte@3.4.0", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ=="],
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
@@ -646,6 +660,8 @@
"react-to-webcomponent": ["react-to-webcomponent@2.0.1", "", { "dependencies": { "@r2wc/core": "^1.0.0" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-jpVztcA0SbV6TnBRV6EFuU+hmuuSIxgEww3wRcbneT0ytqn57y9ygeRfUmhzz8+bYg53u1h4COKk8iAextHE+Q=="],
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
@@ -662,6 +678,8 @@
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"sass": ["sass@1.92.1", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-ffmsdbwqb3XeyR8jJR6KelIXARM9bFQe8A6Q3W4Klmwy5Ckd5gz7jgUNHo4UOqutU5Sk1DtKLbpDP0nLCg1xqQ=="],
"sass-embedded": ["sass-embedded@1.92.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.5.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-all-unknown": "1.92.1", "sass-embedded-android-arm": "1.92.1", "sass-embedded-android-arm64": "1.92.1", "sass-embedded-android-riscv64": "1.92.1", "sass-embedded-android-x64": "1.92.1", "sass-embedded-darwin-arm64": "1.92.1", "sass-embedded-darwin-x64": "1.92.1", "sass-embedded-linux-arm": "1.92.1", "sass-embedded-linux-arm64": "1.92.1", "sass-embedded-linux-musl-arm": "1.92.1", "sass-embedded-linux-musl-arm64": "1.92.1", "sass-embedded-linux-musl-riscv64": "1.92.1", "sass-embedded-linux-musl-x64": "1.92.1", "sass-embedded-linux-riscv64": "1.92.1", "sass-embedded-linux-x64": "1.92.1", "sass-embedded-unknown-all": "1.92.1", "sass-embedded-win32-arm64": "1.92.1", "sass-embedded-win32-x64": "1.92.1" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-28YwLnF5atAhogt3E4hXzz/NB9dwKffyw08a7DEasLh94P7+aELkG3ENSHYCWB9QFN14hYNLfwr9ozUsPDhcDQ=="],
@@ -708,6 +726,8 @@
"set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
@@ -718,6 +738,8 @@
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"style-to-object": ["style-to-object@1.0.9", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw=="],
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "ezpplauncher",
"version": "3.1.3",
"version": "3.1.6",
"description": "",
"type": "module",
"scripts": {
@@ -25,13 +25,13 @@
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.1.11",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-dialog": "^2.6.0",
"@tauri-apps/plugin-fs": "^2.4.5",
"@tauri-apps/plugin-dialog": "^2.7.0",
"@tauri-apps/plugin-fs": "^2.5.0",
"@tauri-apps/plugin-shell": "^2.3.5",
"@types/umami": "^2.10.1",
"animejs": "^4.1.3",
"buffer": "^6.0.3",
"crypto-js": "^4.2.0",
"jszip": "^3.10.1",
"lucide-svelte": "^1.0.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
+69 -179
View File
@@ -43,9 +43,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.100"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "async-broadcast"
@@ -470,15 +470,13 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.42"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-link 0.2.1",
]
@@ -988,16 +986,17 @@ dependencies = [
[[package]]
name = "ezpplauncher"
version = "3.1.3"
version = "3.1.6"
dependencies = [
"anyhow",
"base64 0.22.1",
"byteorder",
"discord-rich-presence",
"futures-util",
"hardware-id",
"md5",
"once_cell",
"open",
"osynic_osudb",
"reqwest",
"serde",
"serde_json",
@@ -1014,7 +1013,7 @@ dependencies = [
"widestring",
"winapi",
"windows-sys 0.61.2",
"winreg 0.55.0",
"winreg 0.56.0",
]
[[package]]
@@ -1614,6 +1613,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]]
name = "httparse"
version = "1.10.1"
@@ -2041,9 +2046,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.179"
version = "0.2.185"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
[[package]]
name = "libloading"
@@ -2055,26 +2060,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "liblzma"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6033b77c21d1f56deeae8014eb9fbe7bdf1765185a6c508b5ca82eeaed7f899"
dependencies = [
"liblzma-sys",
]
[[package]]
name = "liblzma-sys"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f2db66f3268487b5033077f266da6777d057949b8f93c8ad82e441df25e6186"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "libredox"
version = "0.1.12"
@@ -2200,9 +2185,9 @@ dependencies = [
[[package]]
name = "mio"
version = "1.1.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
@@ -2285,15 +2270,6 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "nom"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
dependencies = [
"memchr",
]
[[package]]
name = "ntapi"
version = "0.4.2"
@@ -2374,44 +2350,16 @@ checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
dependencies = [
"bitflags 2.10.0",
"block2 0.6.2",
"libc",
"objc2 0.6.3",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-foundation 0.3.1",
"objc2-quartz-core 0.3.1",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d"
dependencies = [
"bitflags 2.10.0",
"objc2 0.6.3",
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-core-data"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d"
dependencies = [
"bitflags 2.10.0",
"objc2 0.6.3",
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.10.0",
"dispatch2",
@@ -2425,20 +2373,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
dependencies = [
"bitflags 2.10.0",
"dispatch2",
"objc2 0.6.3",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-core-image"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e"
dependencies = [
"objc2 0.6.3",
"objc2-foundation 0.3.1",
]
[[package]]
@@ -2483,35 +2418,14 @@ dependencies = [
[[package]]
name = "objc2-io-kit"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
dependencies = [
"libc",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
dependencies = [
"bitflags 2.10.0",
"objc2 0.6.3",
"objc2-core-foundation",
]
[[package]]
name = "objc2-javascript-core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9052cb1bb50a4c161d934befcf879526fb87ae9a68858f241e693ca46225cf5a"
dependencies = [
"objc2 0.6.3",
"objc2-core-foundation",
]
[[package]]
name = "objc2-metal"
version = "0.2.2"
@@ -2537,28 +2451,6 @@ dependencies = [
"objc2-metal",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
dependencies = [
"bitflags 2.10.0",
"objc2 0.6.3",
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-security"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1f8e0ef3ab66b08c42644dcb34dba6ec0a574bbd8adbb8bdbdc7a2779731a44"
dependencies = [
"bitflags 2.10.0",
"objc2 0.6.3",
"objc2-core-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.1"
@@ -2583,21 +2475,19 @@ dependencies = [
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation 0.3.1",
"objc2-javascript-core",
"objc2-security",
]
[[package]]
name = "once_cell"
version = "1.21.3"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "open"
version = "5.3.3"
version = "5.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc"
checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd"
dependencies = [
"dunce",
"is-wsl",
@@ -2637,18 +2527,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "osynic_osudb"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d7f03567d97cdc0bf54380f0c525a00de1ce58ed9497f2409dfaf6112346b8a"
dependencies = [
"chrono",
"liblzma",
"nom",
"serde",
]
[[package]]
name = "pango"
version = "0.18.3"
@@ -3883,12 +3761,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.6.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -3905,7 +3783,7 @@ dependencies = [
"log",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2-quartz-core 0.2.2",
"objc2-quartz-core",
"raw-window-handle",
"redox_syscall",
"wasm-bindgen",
@@ -4043,9 +3921,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.38.2"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efc19935b4b66baa6f654ac7924c192f55b175c00a7ab72410fc24284dacda8"
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
dependencies = [
"libc",
"memchr",
@@ -4148,9 +4026,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.10.2"
version = "2.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "463ae8677aa6d0f063a900b9c41ecd4ac2b7ca82f0b058cc4491540e55b20129"
checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d"
dependencies = [
"anyhow",
"bytes",
@@ -4163,6 +4041,7 @@ dependencies = [
"gtk",
"heck 0.5.0",
"http",
"http-range",
"jni",
"libc",
"log",
@@ -4199,9 +4078,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.5.5"
version = "2.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca7bd893329425df750813e95bd2b643d5369d929438da96d5bbb7cc2c918f74"
checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d"
dependencies = [
"anyhow",
"cargo_toml",
@@ -4221,9 +4100,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.5.4"
version = "2.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aac423e5859d9f9ccdd32e3cf6a5866a15bedbf25aa6630bcb2acde9468f6ae3"
checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29"
dependencies = [
"base64 0.22.1",
"brotli",
@@ -4248,9 +4127,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.5.4"
version = "2.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b6a1bd2861ff0c8766b1d38b32a6a410f6dc6532d4ef534c47cfb2236092f59"
checksum = "d39b349a98dadaffebb73f0a40dcd1f23c999211e5a2e744403db384d0c33de7"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -4299,9 +4178,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-dialog"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b"
checksum = "a1fa4150c95ae391946cc8b8f905ab14797427caba3a8a2f79628e956da91809"
dependencies = [
"log",
"raw-window-handle",
@@ -4317,13 +4196,15 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
version = "2.4.5"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804"
checksum = "36e1ec28b79f3d0683f4507e1615c36292c0ea6716668770d4396b9b39871ed8"
dependencies = [
"anyhow",
"dunce",
"glob",
"log",
"objc2-foundation 0.3.1",
"percent-encoding",
"schemars 0.8.22",
"serde",
@@ -4360,9 +4241,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-single-instance"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc61e4822b8f74d68278e09161d3e3fdd1b14b9eb781e24edccaabf10c420e8c"
checksum = "a33a5b7d78f0dec4406b003ea87c40bf928d801b6fd9323a556172c91d8712c1"
dependencies = [
"serde",
"serde_json",
@@ -4375,9 +4256,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.10.0"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651"
checksum = "2826d79a3297ed08cd6ea7f412644ef58e32969504bc4fbd8d7dbeabc4445ea2"
dependencies = [
"cookie",
"dpi",
@@ -4400,9 +4281,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.10.0"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314"
checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e"
dependencies = [
"gtk",
"http",
@@ -4410,7 +4291,6 @@ dependencies = [
"log",
"objc2 0.6.3",
"objc2-app-kit",
"objc2-foundation 0.3.1",
"once_cell",
"percent-encoding",
"raw-window-handle",
@@ -4427,9 +4307,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.8.2"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e"
checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d"
dependencies = [
"anyhow",
"brotli",
@@ -4596,9 +4476,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.49.0"
version = "1.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
dependencies = [
"bytes",
"libc",
@@ -4613,9 +4493,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
@@ -5776,6 +5656,16 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "winreg"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
dependencies = [
"cfg-if",
"windows-sys 0.61.2",
]
[[package]]
name = "wit-bindgen"
version = "0.46.0"
+23 -12
View File
@@ -1,12 +1,22 @@
[package]
name = "ezpplauncher"
version = "3.1.3"
version = "3.1.6"
description = "EZPPLauncher redefined."
authors = ["HorizonCode"]
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.dev]
incremental = true # Compile your binary in smaller steps.
[profile.release]
codegen-units = 1 # Allows LLVM to perform better optimization.
lto = true # Enables link-time-optimizations.
opt-level = "s" # Prioritizes small binary size. Use `3` if you prefer speed.
panic = "abort" # Higher performance by disabling panic handlers.
strip = true # Ensures debug symbols are removed.
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
@@ -15,35 +25,36 @@ name = "ezpplauncher_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.5.5", features = [] }
tauri-build = { version = "2.5.6", features = [] }
[dependencies]
tauri = { version = "2.10.2", features = [] }
tauri = { version = "2.10.3", features = ["protocol-asset"] }
tauri-plugin-shell = "2.3.5"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
serde_repr = "0.1.20"
tauri-plugin-dialog = "2.6.0"
tauri-plugin-fs = "2.4.5"
tauri-plugin-dialog = "2.7.0"
tauri-plugin-fs = "2.5.0"
hardware-id = "0.3.0"
tauri-plugin-cors-fetch = "5.0.0"
sysinfo = "0.38.2"
sysinfo = "0.38.4"
reqwest = { version = "0.13.2", features = ["json", "stream", "query"] }
md5 = "0.8.0"
tokio = { version = "1.48.0", features = ["full"] }
open = "5.3.3"
tokio = { version = "1.52.1", features = ["full"] }
open = "5.3.4"
discord-rich-presence = "1.1.0"
once_cell = "1.21.3"
once_cell = "1.21.4"
futures-util = "0.3.32"
osynic_osudb = "0.1.4"
byteorder = "1.5.0"
anyhow = "1.0.102"
[target.'cfg(windows)'.dependencies]
winreg = "0.55.0"
winreg = "0.56.0"
winapi = { version = "0.3", features = ["winuser", "wincrypt", "memoryapi", "winbase", "dpapi"] }
base64 = "0.22.1"
widestring = "1.2.1"
windows-sys = "0.61.2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-single-instance = "2.4.0"
tauri-plugin-single-instance = "2.4.1"
+95 -23
View File
@@ -2,7 +2,9 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"windows": [
"main"
],
"permissions": [
"core:default",
"shell:allow-open",
@@ -19,58 +21,128 @@
{
"identifier": "fs:allow-read",
"allow": [
{ "path": "$HOME/.ezpplauncher" },
{ "path": "$HOME/.ezpplauncher/*" },
{ "path": "$HOME/.ezpplauncher/**/*" }
{
"path": "$HOME/.ezpplauncher"
},
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
]
},
{
"identifier": "fs:allow-write",
"allow": [
{ "path": "$HOME/.ezpplauncher" },
{ "path": "$HOME/.ezpplauncher/*" },
{ "path": "$HOME/.ezpplauncher/**/*" }
{
"path": "$HOME/.ezpplauncher"
},
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
]
},
{
"identifier": "fs:allow-exists",
"allow": [
{ "path": "$HOME/.ezpplauncher" },
{ "path": "$HOME/.ezpplauncher/*" },
{ "path": "$HOME/.ezpplauncher/**/*" }
{
"path": "$HOME/.ezpplauncher"
},
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
]
},
{
"identifier": "fs:allow-read-file",
"allow": [
{ "path": "$HOME/.ezpplauncher" },
{ "path": "$HOME/.ezpplauncher/*" },
{ "path": "$HOME/.ezpplauncher/**/*" }
{
"path": "$HOME/.ezpplauncher"
},
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
]
},
{
"identifier": "fs:allow-write-file",
"allow": [
{ "path": "$HOME/.ezpplauncher" },
{ "path": "$HOME/.ezpplauncher/*" },
{ "path": "$HOME/.ezpplauncher/**/*" }
{
"path": "$HOME/.ezpplauncher"
},
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
]
},
{
"identifier": "fs:allow-read-text-file",
"allow": [
{ "path": "$HOME/.ezpplauncher" },
{ "path": "$HOME/.ezpplauncher/*" },
{ "path": "$HOME/.ezpplauncher/**/*" }
{
"path": "$HOME/.ezpplauncher"
},
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
]
},
{
"identifier": "fs:allow-mkdir",
"allow": [
{ "path": "$HOME/.ezpplauncher" },
{ "path": "$HOME/.ezpplauncher/*" },
{ "path": "$HOME/.ezpplauncher/**/*" }
{
"path": "$HOME/.ezpplauncher"
},
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
]
},
{
"identifier": "fs:allow-read-dir",
"allow": [
{
"path": "$HOME/.ezpplauncher"
},
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
]
},
{
"identifier": "fs:allow-remove",
"allow": [
{
"path": "$HOME/.ezpplauncher"
},
{
"path": "$HOME/.ezpplauncher/*"
},
{
"path": "$HOME/.ezpplauncher/**/*"
}
]
}
]
}
}
+120 -105
View File
@@ -1,5 +1,4 @@
use hardware_id::get_id;
use osynic_osudb::entity::osu::osudb::OsuDB;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
@@ -13,6 +12,7 @@ use tokio::io::AsyncWriteExt;
use tokio::process::Command;
use tokio::time::{Duration, sleep};
use crate::osudb::parse_osudb;
use crate::presence;
use crate::utils::{
check_folder_completeness, encrypt_password, get_osu_config, get_osu_user_config,
@@ -161,10 +161,10 @@ pub async fn get_beatmapsets_count(folder: String) -> Option<u64> {
if !osu_db_path.exists() {
return Some(0);
}
let mut osudb = OsuDB::from_file(&osu_db_path).unwrap();
let beatmaps = osudb.beatmaps.iter_mut();
let beatmap_sets = beatmaps.map(|b| b.beatmapset_id).collect::<HashSet<_>>();
return Some(beatmap_sets.len() as u64);
let osu_db_bytes = fs::read(osu_db_path).await.ok()?;
let osu_db = parse_osudb(osu_db_bytes).ok()?;
let beatmap_sets = osu_db.beatmaps.iter().map(|b| b.beatmapset_id).collect::<HashSet<_>>().len();
Some(beatmap_sets as u64)
}
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -590,7 +590,6 @@ pub async fn download_ezpp_launcher_update_files(
#[serde(tag = "type", content = "details")]
pub enum ReplaceUIError {
FileNotFound(String),
PermissionDenied(String),
IoError(String),
}
@@ -608,92 +607,86 @@ pub fn replace_ui_files(folder: String, revert: bool) -> Result<(), ReplaceUIErr
let osu_seasonal_bak = osu_path.join("osu!seasonal.dll.bak");
let osu_gameplay_bak = osu_path.join("osu!gameplay.dll.bak");
let create_link = |target: &PathBuf, link: &PathBuf| -> Result<(), ReplaceUIError> {
if !target.exists() {
return Err(ReplaceUIError::FileNotFound(target.display().to_string()));
let temp_file = osu_path.join(".ezpplauncher_files");
let copy_file = |source: &PathBuf, dest: &PathBuf, name: &str| -> Result<(), ReplaceUIError> {
if !source.exists() {
return Err(ReplaceUIError::FileNotFound(source.display().to_string()));
}
if link.exists() || link.is_symlink() {
std::fs::remove_file(link).ok();
if dest.exists() {
std::fs::remove_file(dest).map_err(|e| {
ReplaceUIError::IoError(format!("Failed to remove {}: {}", name, e))
})?;
}
#[cfg(windows)]
{
let result = if target.is_dir() {
std::os::windows::fs::symlink_dir(target, link)
} else {
std::os::windows::fs::symlink_file(target, link)
};
if result.is_ok() {
return Ok(());
}
}
#[cfg(not(windows))]
{
if std::os::unix::fs::symlink(target, link).is_ok() {
return Ok(());
}
}
std::fs::hard_link(target, link).map_err(|e| match e.kind() {
std::io::ErrorKind::NotFound => {
ReplaceUIError::FileNotFound(target.display().to_string())
}
std::io::ErrorKind::PermissionDenied => {
ReplaceUIError::PermissionDenied(target.display().to_string())
}
_ => ReplaceUIError::IoError(e.to_string()),
})
};
let remove_link = |link: &PathBuf| -> Result<(), ReplaceUIError> {
if link.exists() || link.is_symlink() {
std::fs::remove_file(link).map_err(|e| ReplaceUIError::IoError(e.to_string()))?;
}
std::fs::copy(source, dest)
.map_err(|e| ReplaceUIError::IoError(format!("Failed to copy {}: {}", name, e)))?;
Ok(())
};
let restore_backup =
|bak: &PathBuf, dest: &PathBuf, name: &str| -> Result<(), ReplaceUIError> {
if bak.exists() {
if dest.exists() {
std::fs::remove_file(dest).map_err(|e| {
ReplaceUIError::IoError(format!("Failed to remove {}: {}", name, e))
})?;
}
std::fs::rename(bak, dest).map_err(|e| {
ReplaceUIError::IoError(format!("Failed to rename {} from backup: {}", name, e))
})?;
}
Ok(())
};
if !revert {
if osu_ui.exists() && !osu_ui_bak.exists() {
std::fs::rename(&osu_ui, &osu_ui_bak)
.map_err(|e| ReplaceUIError::IoError(e.to_string()))?;
std::fs::rename(&osu_ui, &osu_ui_bak).map_err(|e| {
ReplaceUIError::IoError(format!("Failed to backup osu!ui.dll: {}", e))
})?;
}
if osu_seasonal.exists() && !osu_seasonal_bak.exists() {
std::fs::rename(&osu_seasonal, &osu_seasonal_bak)
.map_err(|e| ReplaceUIError::IoError(e.to_string()))?;
std::fs::rename(&osu_seasonal, &osu_seasonal_bak).map_err(|e| {
ReplaceUIError::IoError(format!("Failed to backup osu!seasonal.dll: {}", e))
})?;
}
if osu_gameplay.exists() && !osu_gameplay_bak.exists() {
std::fs::rename(&osu_gameplay, &osu_gameplay_bak)
.map_err(|e| ReplaceUIError::IoError(e.to_string()))?;
std::fs::rename(&osu_gameplay, &osu_gameplay_bak).map_err(|e| {
ReplaceUIError::IoError(format!("Failed to backup osu!gameplay.dll: {}", e))
})?;
}
create_link(&ezpp_ui, &osu_ui)?;
create_link(&ezpp_seasonal, &osu_seasonal)?;
create_link(&ezpp_gameplay, &osu_gameplay)?;
copy_file(&ezpp_ui, &osu_ui, "osu!ui.dll")?;
copy_file(&ezpp_seasonal, &osu_seasonal, "osu!seasonal.dll")?;
copy_file(&ezpp_gameplay, &osu_gameplay, "osu!gameplay.dll")?;
std::fs::write(&temp_file, b"1")
.map_err(|e| ReplaceUIError::IoError(format!("Failed to write marker file: {}", e)))?;
} else {
remove_link(&osu_ui)?;
remove_link(&osu_seasonal)?;
remove_link(&osu_gameplay)?;
restore_backup(&osu_ui_bak, &osu_ui, "osu!ui.dll")?;
restore_backup(&osu_seasonal_bak, &osu_seasonal, "osu!seasonal.dll")?;
restore_backup(&osu_gameplay_bak, &osu_gameplay, "osu!gameplay.dll")?;
if osu_ui_bak.exists() {
std::fs::rename(&osu_ui_bak, &osu_ui)
.map_err(|e| ReplaceUIError::IoError(e.to_string()))?;
}
if osu_seasonal_bak.exists() {
std::fs::rename(&osu_seasonal_bak, &osu_seasonal)
.map_err(|e| ReplaceUIError::IoError(e.to_string()))?;
}
if osu_gameplay_bak.exists() {
std::fs::rename(&osu_gameplay_bak, &osu_gameplay)
.map_err(|e| ReplaceUIError::IoError(e.to_string()))?;
if osu_ui.exists() && osu_seasonal.exists() && osu_gameplay.exists() {
std::fs::remove_file(&temp_file).map_err(|e| {
ReplaceUIError::IoError(format!("Failed to remove marker file: {}", e))
})?;
}
}
Ok(())
}
#[derive(Serialize)]
#[serde(tag = "type", content = "details")]
pub enum CorruptionCheckError {
IoError(String),
FileNotFound(String),
}
#[tauri::command]
pub async fn check_for_corruption(folder: String) -> Result<bool, String> {
pub async fn check_for_corruption(folder: String) -> Result<bool, CorruptionCheckError> {
let osu_path = PathBuf::from(folder);
let osu_ui = osu_path.join("osu!ui.dll");
let osu_gameplay = osu_path.join("osu!gameplay.dll");
@@ -703,47 +696,69 @@ pub async fn check_for_corruption(folder: String) -> Result<bool, String> {
let osu_gameplay_bak = osu_path.join("osu!gameplay.dll.bak");
let osu_seasonal_bak = osu_path.join("osu!seasonal.dll.bak");
let check_and_restore = async |path: &PathBuf, bak: &PathBuf| -> Result<bool, String> {
if path.exists() && bak.exists() {
fs::remove_file(path).await.map_err(|e| e.to_string())?;
fs::rename(bak, path).await.map_err(|e| e.to_string())?;
Ok(false)
} else if path.exists() {
let is_symlink = || -> bool {
if let Ok(meta) = std::fs::symlink_metadata(path) {
return meta.file_type().is_symlink();
}
false
};
let temp_file = osu_path.join(".ezpplauncher_files");
#[cfg(unix)]
let is_hard_link = || -> bool {
use std::os::unix::fs::MetadataExt;
if let Ok(meta) = std::fs::symlink_metadata(path) {
return meta.len() == 0 && meta.nlink() > 1;
}
false
};
#[cfg(windows)]
let is_hard_link = || -> bool {
false
};
if is_symlink() || is_hard_link() {
return Ok(true);
if temp_file.exists() {
if osu_ui_bak.exists() {
if osu_ui.exists() {
fs::remove_file(&osu_ui).await.map_err(|e| {
CorruptionCheckError::IoError(format!("Failed to remove osu!ui.dll: {}", e))
})?;
}
Ok(false)
} else {
Ok(true)
fs::rename(&osu_ui_bak, &osu_ui).await.map_err(|e| {
CorruptionCheckError::IoError(format!(
"Failed to restore osu!ui.dll from backup: {}",
e
))
})?;
}
};
if osu_gameplay_bak.exists() {
if osu_gameplay.exists() {
fs::remove_file(&osu_gameplay).await.map_err(|e| {
CorruptionCheckError::IoError(format!(
"Failed to remove osu!gameplay.dll: {}",
e
))
})?;
}
let ui_corrupted = check_and_restore(&osu_ui, &osu_ui_bak).await?;
let gameplay_corrupted = check_and_restore(&osu_gameplay, &osu_gameplay_bak).await?;
let seasonal_corrupted = check_and_restore(&osu_seasonal, &osu_seasonal_bak).await?;
fs::rename(&osu_gameplay_bak, &osu_gameplay)
.await
.map_err(|e| {
CorruptionCheckError::IoError(format!(
"Failed to restore osu!gameplay.dll from backup: {}",
e
))
})?;
}
if osu_seasonal_bak.exists() {
if osu_seasonal.exists() {
fs::remove_file(&osu_seasonal).await.map_err(|e| {
CorruptionCheckError::IoError(format!(
"Failed to remove osu!seasonal.dll: {}",
e
))
})?;
}
Ok(ui_corrupted || gameplay_corrupted || seasonal_corrupted)
fs::rename(&osu_seasonal_bak, &osu_seasonal)
.await
.map_err(|e| {
CorruptionCheckError::IoError(format!(
"Failed to restore osu!seasonal.dll from backup: {}",
e
))
})?;
}
return Ok(false);
}
if !osu_ui.exists() || !osu_gameplay.exists() || !osu_seasonal.exists() {
return Ok(true);
}
Ok(false)
}
#[tauri::command]
@@ -913,4 +928,4 @@ pub async fn install_ezpp_launcher_update(app: AppHandle) -> Result<(), String>
#[tauri::command]
pub async fn install_ezpp_launcher_update(_app: AppHandle) -> Result<(), String> {
Ok(())
}
}
+2 -1
View File
@@ -4,6 +4,7 @@ use tauri::Manager;
pub mod commands;
pub mod presence;
pub mod utils;
pub mod osudb;
use crate::commands::{
check_for_corruption, download_ezpp_launcher_update, download_ezpp_launcher_update_files,
encrypt_string, exit, find_osu_installation, get_beatmapsets_count,
@@ -19,7 +20,7 @@ use crate::commands::{
pub fn run() {
#[cfg(target_os = "linux")]
unsafe {
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
// std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
}
let mut builder = tauri::Builder::default();
+370
View File
@@ -0,0 +1,370 @@
use std::io::{Cursor, Read};
use anyhow::{Result, anyhow};
use byteorder::{LittleEndian, ReadBytesExt};
use std::collections::HashMap;
pub struct OsuReader {
cursor: Cursor<Vec<u8>>,
}
impl OsuReader {
pub fn new(data: Vec<u8>) -> Self {
Self {
cursor: Cursor::new(data),
}
}
fn read_i16(&mut self) -> Result<i16> {
Ok(self.cursor.read_i16::<LittleEndian>()?)
}
fn read_i32(&mut self) -> Result<i32> {
Ok(self.cursor.read_i32::<LittleEndian>()?)
}
fn read_i64(&mut self) -> Result<i64> {
Ok(self.cursor.read_i64::<LittleEndian>()?)
}
fn read_u8(&mut self) -> Result<u8> {
Ok(self.cursor.read_u8()?)
}
fn read_f32(&mut self) -> Result<f32> {
Ok(self.cursor.read_f32::<LittleEndian>()?)
}
fn read_f64(&mut self) -> Result<f64> {
Ok(self.cursor.read_f64::<LittleEndian>()?)
}
fn read_bool(&mut self) -> Result<bool> {
Ok(self.read_u8()? != 0)
}
fn read_string(&mut self) -> Result<Option<String>> {
let indicator = self.read_u8()?;
if indicator == 0 {
return Ok(None);
}
if indicator != 0x0B {
return Err(anyhow!("Invalid osu string indicator"));
}
let len = self.read_uleb128()? as usize;
let mut buf = vec![0; len];
self.cursor.read_exact(&mut buf)?;
Ok(Some(String::from_utf8(buf)?))
}
fn read_uleb128(&mut self) -> Result<u64> {
let mut result = 0;
let mut shift = 0;
loop {
let byte = self.read_u8()?;
result |= ((byte & 0x7F) as u64) << shift;
if (byte & 0x80) == 0 {
break;
}
shift += 7;
}
Ok(result)
}
}
#[derive(Debug)]
pub struct TimingPoint {
pub bpm: f64,
pub offset: f64,
pub inherited: bool,
}
#[derive(Debug)]
pub struct Beatmap {
pub artist_name: Option<String>,
pub artist_unicode: Option<String>,
pub song_title: Option<String>,
pub song_title_unicode: Option<String>,
pub creator: Option<String>,
pub difficulty: Option<String>,
pub audio: Option<String>,
pub md5: Option<String>,
pub osu_file_name: Option<String>,
pub ranked_status: u8,
pub hitcircles: i16,
pub slider_count: i16,
pub spinner_count: i16,
pub last_modification: i64,
pub approach_rate: f32,
pub circle_size: f32,
pub hp_drain: f32,
pub overall_difficulty: f32,
pub slider_velocity: f64,
pub difficulties: HashMap<i32, f64>,
pub drain_time: i32,
pub total_time: i32,
pub preview_offset: i32,
pub beatmap_id: i32,
pub beatmapset_id: i32,
pub thread_id: i32,
pub grade_standard: u8,
pub grade_taiko: u8,
pub grade_catch: u8,
pub grade_mania: u8,
pub local_beatmap_offset: i16,
pub stack_leniency: f32,
pub mode: u8,
pub song_source: Option<String>,
pub song_tags: Option<String>,
pub online_beatmap_offset: i16,
pub title_font: Option<String>,
pub unplayed: bool,
pub last_played: i64,
pub osz2: bool,
pub folder_name: Option<String>,
pub last_checked_against_repository: i64,
pub ignore_sound: bool,
pub ignore_skin: bool,
pub disable_storyboard: bool,
pub disable_video: bool,
pub visual_override: bool,
pub last_modification_time: i32,
pub mania_scroll_speed: u8,
pub timing_points: Vec<TimingPoint>,
}
#[derive(Debug)]
pub struct OsuDbData {
pub osuver: i32,
pub folder_count: i32,
pub is_unlocked: bool,
pub date_unlock_ticks: i64,
pub username: Option<String>,
pub beatmaps: Vec<Beatmap>,
pub userperms: i32,
}
//
// =========================
// PARSER
// =========================
//
pub fn parse_osudb(bytes: Vec<u8>) -> Result<OsuDbData> {
let mut reader = OsuReader::new(bytes);
let osuver = reader.read_i32()?;
let folder_count = reader.read_i32()?;
let is_unlocked = reader.read_bool()?;
let date_unlock_ticks = reader.read_i64()?;
let username = reader.read_string()?;
let beatmaps_count = reader.read_i32()?;
let mut beatmaps = Vec::new();
for _ in 0..beatmaps_count {
if osuver < 20191107 {
reader.read_i32()?; // entry size
}
let artist_name = reader.read_string()?;
let artist_unicode = reader.read_string()?;
let song_title = reader.read_string()?;
let song_title_unicode = reader.read_string()?;
let creator = reader.read_string()?;
let difficulty = reader.read_string()?;
let audio = reader.read_string()?;
let md5 = reader.read_string()?;
let osu_file_name = reader.read_string()?;
let ranked_status = reader.read_u8()?;
let hitcircles = reader.read_i16()?;
let slider_count = reader.read_i16()?;
let spinner_count = reader.read_i16()?;
let last_modification = reader.read_i64()?;
let approach_rate = if osuver < 20140609 {
reader.read_u8()? as f32
} else {
reader.read_f32()?
};
let circle_size = if osuver < 20140609 {
reader.read_u8()? as f32
} else {
reader.read_f32()?
};
let hp_drain = if osuver < 20140609 {
reader.read_u8()? as f32
} else {
reader.read_f32()?
};
let overall_difficulty = if osuver < 20140609 {
reader.read_u8()? as f32
} else {
reader.read_f32()?
};
let slider_velocity = reader.read_f64()?;
let mut difficulties: HashMap<i32, f64> = HashMap::new();
if osuver >= 20140609 {
for _ in 0..4 {
let len = reader.read_i32()?;
for _ in 0..len {
reader.read_u8()?;
let mode = reader.read_i32()?;
reader.read_u8()?;
let diff = if osuver > 20250107 {
reader.read_f32()? as f64
} else {
reader.read_f64()?
};
difficulties.insert(mode, diff);
}
}
}
let drain_time = reader.read_i32()?;
let total_time = reader.read_i32()?;
let preview_offset = reader.read_i32()?;
let timing_len = reader.read_i32()?;
let mut timing_points = Vec::new();
for _ in 0..timing_len {
timing_points.push(TimingPoint {
bpm: reader.read_f64()?,
offset: reader.read_f64()?,
inherited: reader.read_bool()?,
});
}
let beatmap_id = reader.read_i32()?;
let beatmapset_id = reader.read_i32()?;
let thread_id = reader.read_i32()?;
let grade_standard = reader.read_u8()?;
let grade_taiko = reader.read_u8()?;
let grade_catch = reader.read_u8()?;
let grade_mania = reader.read_u8()?;
let local_beatmap_offset = reader.read_i16()?;
let stack_leniency = reader.read_f32()?;
let mode = reader.read_u8()?;
let song_source = reader.read_string()?;
let song_tags = reader.read_string()?;
let online_beatmap_offset = reader.read_i16()?;
let title_font = reader.read_string()?;
let unplayed = reader.read_bool()?;
let last_played = reader.read_i64()?;
let osz2 = reader.read_bool()?;
let folder_name = reader.read_string()?;
let last_checked_against_repository = reader.read_i64()?;
let ignore_sound = reader.read_bool()?;
let ignore_skin = reader.read_bool()?;
let disable_storyboard = reader.read_bool()?;
let disable_video = reader.read_bool()?;
let visual_override = reader.read_bool()?;
if osuver < 20140609 {
reader.read_i16()?;
}
let last_modification_time = reader.read_i32()?;
let mania_scroll_speed = reader.read_u8()?;
beatmaps.push(Beatmap {
artist_name,
artist_unicode,
song_title,
song_title_unicode,
creator,
difficulty,
audio,
md5,
osu_file_name,
ranked_status,
hitcircles,
slider_count,
spinner_count,
last_modification,
approach_rate,
circle_size,
hp_drain,
overall_difficulty,
slider_velocity,
difficulties,
drain_time,
total_time,
preview_offset,
beatmap_id,
beatmapset_id,
thread_id,
grade_standard,
grade_taiko,
grade_catch,
grade_mania,
local_beatmap_offset,
stack_leniency,
mode,
song_source,
song_tags,
online_beatmap_offset,
title_font,
unplayed,
last_played,
osz2,
folder_name,
last_checked_against_repository,
ignore_sound,
ignore_skin,
disable_storyboard,
disable_video,
visual_override,
last_modification_time,
mania_scroll_speed,
timing_points,
});
}
let userperms = reader.read_i32()?;
Ok(OsuDbData {
osuver,
folder_count,
is_unlocked,
date_unlock_ticks,
username,
beatmaps,
userperms,
})
}
+50 -28
View File
@@ -1,6 +1,6 @@
use discord_rich_presence::{
activity::{Activity, Assets, Button, Timestamps},
DiscordIpc, DiscordIpcClient,
activity::{Activity, Assets, Button, Timestamps},
};
use once_cell::sync::Lazy;
use std::future::Future;
@@ -91,36 +91,53 @@ impl PresenceActor {
}
}
fn handle_connect(&mut self, responder: oneshot::Sender<bool>) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
fn handle_connect(
&mut self,
responder: oneshot::Sender<bool>,
) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
if self.client.is_some() {
let _ = responder.send(true);
return;
}
println!("Actor: Connecting to Discord...");
let mut dc_client = DiscordIpcClient::new("1032772293220384808");
match dc_client.connect() {
Ok(()) => {
self.client = Some(dc_client);
println!("Actor: Connected successfully.");
self.handle_update().await;
if self.client.is_some() {
let _ = responder.send(true);
return;
}
Err(e) => {
eprintln!("Failed to connect to Discord: {:?}. Retrying in 5s...", e);
let tx = PRESENCE_TX.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(5)).await;
let (resp_tx, _) = oneshot::channel();
let _ = tx.send(PresenceCommand::Connect(resp_tx)).await;
});
if !responder.is_closed() {
let _ = responder.send(false);
println!("Actor: Connecting to Discord...");
let mut dc_client = DiscordIpcClient::new("1032772293220384808");
match dc_client.connect() {
Ok(()) => {
self.client = Some(dc_client);
println!("Actor: Connected successfully.");
self.handle_update().await;
let _ = responder.send(true);
}
Err(e) => {
eprintln!(
"Failed to connect to Discord: {:?}. Scheduling bounded retry...",
e
);
let tx = PRESENCE_TX.clone();
tokio::spawn(async move {
for attempt in 1..=3 {
tokio::time::sleep(Duration::from_secs(5 * attempt)).await;
let (resp_tx, rx) = oneshot::channel();
if tx.send(PresenceCommand::Connect(resp_tx)).await.is_ok() {
if rx.await.unwrap_or(false) {
println!("Actor: Reconnection attempt {} succeeded", attempt);
break;
}
println!(
"Actor: Reconnection attempt {} failed, retrying...",
attempt
);
}
}
});
if !responder.is_closed() {
let _ = responder.send(false);
}
}
}
}
})
}
@@ -164,8 +181,13 @@ impl PresenceActor {
eprintln!("Failed to set activity, disconnecting: {:?}", e);
self.client = None;
let (tx, _) = oneshot::channel();
self.handle_connect(tx).await;
// Don't await reconnection here - send command to avoid blocking the actor
let tx = PRESENCE_TX.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(1)).await;
let (resp_tx, _) = oneshot::channel();
let _ = tx.send(PresenceCommand::Connect(resp_tx)).await;
});
} else {
println!("Actor: Presence updated successfully.");
}
+9 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "EZPPLauncher",
"version": "3.1.3",
"version": "3.1.6",
"identifier": "farm.EZPPFarm.Launcher",
"mainBinaryName": "ezpplauncher",
"build": {
@@ -23,7 +23,13 @@
],
"withGlobalTauri": true,
"security": {
"csp": null
"csp": null,
"assetProtocol": {
"enable": true,
"scope": [
"$HOME/.ezpplauncher/themes/**"
]
}
}
},
"bundle": {
@@ -44,6 +50,7 @@
"nsis": {
"installMode": "currentUser",
"compression": "lzma",
"installerIcon": "icons/icon.ico",
"installerHooks": "./nsis/nsis-hooks.nsh"
}
},
+56
View File
@@ -145,6 +145,62 @@
--color-sidebar-ring: var(--sidebar-ring);
}
main[data-theme="execution_clap"] {
--primary: #fc112b;
--primary-foreground: #fff;
--ring: #fc112b;
--color-primary-50: #fef2f2;
--color-primary-100: #fee2e2;
--color-primary-200: #fecaca;
--color-primary-300: #fca5a5;
--color-primary-400: #f87171;
--color-primary-500: #ef4444;
--color-primary-600: #dc2626;
--color-primary-700: #b91c1c;
--color-primary-800: #991b1b;
--color-primary-900: #7f1d1d;
--color-primary-950: #450a0a;
--bg-primary-50: #fef2f2;
--bg-primary-100: #fee2e2;
--bg-primary-200: #fecaca;
--bg-primary-300: #fca5a5;
--bg-primary-400: #f87171;
--bg-primary-500: #ef4444;
--bg-primary-600: #dc2626;
--bg-primary-700: #b91c1c;
--bg-primary-800: #991b1b;
--bg-primary-900: #7f1d1d;
--bg-primary-950: #450a0a;
}
main[data-theme="lost_umbrella"] {
--primary: #e5e5e5;
--primary-foreground: #000;
--ring: #e5e5e5;
--color-primary-50: #0d0d0d;
--color-primary-100: #1a1a1a;
--color-primary-200: #333333;
--color-primary-300: #4d4d4d;
--color-primary-400: #666666;
--color-primary-500: #808080;
--color-primary-600: #999999;
--color-primary-700: #b3b3b3;
--color-primary-800: #cccccc;
--color-primary-900: #e6e6e6;
--color-primary-950: #f2f2f2;
--bg-primary-50: #0d0d0d;
--bg-primary-100: #1a1a1a;
--bg-primary-200: #333333;
--bg-primary-300: #4d4d4d;
--bg-primary-400: #666666;
--bg-primary-500: #808080;
--bg-primary-600: #999999;
--bg-primary-700: #b3b3b3;
--bg-primary-800: #cccccc;
--bg-primary-900: #e6e6e6;
--bg-primary-950: #f2f2f2;
}
@layer base {
* {
@apply border-border outline-ring/50;
Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
@@ -1,7 +1,6 @@
<script lang="ts">
import NyanCat from '$assets/nyan-cat.gif';
import NyanCat from '$assets/themes/nyan_cat/image.gif';
import { reduceAnimations } from '@/userSettings';
import { fade } from 'svelte/transition';
//TODO: maybe dynamic background images fetched from ezpp?
@@ -17,7 +17,7 @@
</script>
<button
class="group flex items-center h-12 rounded-full bg-primary-300 disabled:bg-primary-300/70 shadow-lg shadow-primary/25 transition-all not-disabled:hover:shadow-primary/40 not-disabled:hover:brightness-110 not-disabled:active:scale-[0.98] cursor-pointer w-fit"
class="group flex items-center h-12 rounded-full bg-primary disabled:bg-primary/70 shadow-lg shadow-primary/25 transition-all not-disabled:hover:shadow-primary/40 not-disabled:hover:brightness-110 not-disabled:active:scale-[0.98] cursor-pointer w-fit"
disabled={prop.disabled}
onclick={prop.onClick}
>
@@ -74,12 +74,18 @@
<div class="wrapper" bind:this={wrapper}>
<!-- Scrollable content -->
<div class={`content ${className}`} bind:this={container} onscroll={updateScrollbar}>
<div
class={`content ${className}`}
bind:this={container}
onscroll={updateScrollbar}
id="scrollable-content"
>
{@render children?.()}
</div>
<!-- Custom scrollbar -->
<div class="scrollbar" style="top: {topOffset}px;">
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="thumb"
bind:this={thumb}
@@ -87,6 +93,7 @@
onpointermove={drag}
onpointerup={endDrag}
onpointercancel={endDrag}
aria-controls="scrollable-content"
style="height:{thumbHeight}px; transform:translateY({thumbTop}px);"
></div>
</div>
@@ -117,8 +124,8 @@
position: absolute;
right: 2px;
bottom: 0;
width: 12px;
background: var(--color-theme-950);
width: 8px;
background: transparent;
border-radius: 999px;
}
@@ -126,7 +133,7 @@
.thumb {
position: absolute;
width: 100%;
background: var(--color-theme-800);
background: var(--color-theme-700);
border-radius: 999px;
cursor: grab;
transition: background 0.15s;
@@ -17,7 +17,7 @@
bind:ref
{value}
class={cn(
'data-[highlighted]:bg-primary-300 data-[highlighted]:text-primary-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'data-[highlighted]:bg-primary data-[highlighted]:text-primary-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...restProps}
+115 -15
View File
@@ -1,14 +1,13 @@
<script lang="ts">
import Minimize from 'lucide-svelte/icons/minus';
import Close from 'lucide-svelte/icons/x';
import Logo from '$assets/logo.png';
import Volume2 from 'lucide-svelte/icons/volume-2';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { onMount } from 'svelte';
import { launcherVersion } from '@/global';
import Badge from '../badge/badge.svelte';
import { reduceAnimations } from '@/userSettings';
import { custom_theme_audio_playing, custom_theme_volume } from '@/global';
import { reduceAnimations, userSettings } from '@/userSettings';
import { Volume, Volume1, VolumeOff } from 'lucide-svelte';
onMount(() => {
const appWindow = getCurrentWindow();
@@ -21,14 +20,67 @@
</script>
<div data-tauri-drag-region class="titlebar z-[100]">
{#if $custom_theme_audio_playing}
{#if $custom_theme_audio_playing}
<div class="relative flex items-center justify-center group">
<div class="relative flex items-center justify-center p-1 cursor-pointer">
<div
class="absolute inset-0 bg-background rounded
scale-80 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}"
></div>
{#if $custom_theme_volume > 0.7}
<Volume2 class="z-10 pointer-events-none" size={14} />
{:else if $custom_theme_volume >= 0.15}
<Volume1 class="z-10 pointer-events-none" size={14} />
{:else if $custom_theme_volume > 0}
<Volume class="z-10 pointer-events-none" size={14} />
{:else}
<VolumeOff class="z-10 pointer-events-none" size={14} />
{/if}
</div>
<div
class="absolute top-full right-0 translate-x-[7px]
opacity-0 invisible pointer-events-none -translate-y-2
group-hover:opacity-100 group-hover:visible group-hover:pointer-events-auto group-hover:translate-y-0
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
z-50"
>
<div class="h-2 w-full"></div>
<div
class="flex items-center justify-center h-[120px] w-[35px]
bg-black/20 backdrop-blur-sm border border-white/10 rounded-xl"
>
<input
type="range"
min="0"
max="1"
step="0.01"
bind:value={$custom_theme_volume}
on:input={() => {
$userSettings.value('volume').set($custom_theme_volume);
$userSettings.save();
}}
class="volume-slider"
/>
</div>
</div>
</div>
{/if}
{/if}
<div class="relative flex items-center justify-center p-1 group" id="titlebar-minimize">
<div
class="absolute inset-0 bg-background rounded
scale-80 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
origin-center"
scale-80 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
origin-center"
></div>
<Minimize class="z-10 pointer-events-none" size={14} />
@@ -36,11 +88,11 @@
<div class="relative flex items-center justify-center p-1 group" id="titlebar-close">
<div
class="absolute inset-0 bg-red-500 rounded
scale-80 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
origin-center"
scale-80 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
origin-center"
></div>
<Close class="z-10 pointer-events-none" size={14} />
@@ -63,4 +115,52 @@
padding-right: 5px;
pointer-events: auto;
}
.volume-slider {
appearance: none;
-webkit-appearance: none;
width: 100px; /* becomes vertical */
height: 6px;
background: transparent;
cursor: pointer;
transform: rotate(-90deg);
}
/* track */
.volume-slider::-webkit-slider-runnable-track {
height: 6px;
background: rgba(255, 255, 255, 0.25);
border-radius: 999px;
}
/* thumb */
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
border: none;
margin-top: -4px; /* center thumb */
}
/* Firefox */
.volume-slider::-moz-range-track {
height: 6px;
background: rgba(255, 255, 255, 0.25);
border-radius: 999px;
}
.volume-slider::-moz-range-thumb {
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
border: none;
}
</style>
+13 -2
View File
@@ -3,7 +3,9 @@ import { ezppfarm } from './api/ezpp';
import type { Component } from 'svelte';
import Loading from '../screens/Loading.svelte';
import type { Release } from './types';
import type { Theme } from './themes';
import { browser } from '$app/environment';
import { setGlobalVolume } from './utils';
export const currentView = writable<Component>(Loading);
export const platform = writable<string>('');
@@ -24,7 +26,16 @@ export const currentLoadingInfo = writable<string>('Initializing...');
export const firstStartup = writable<boolean>(false);
export const launching = writable<boolean>(false);
export const nyanCatSong = writable<HTMLAudioElement | undefined>(undefined);
export const custom_themes = writable<Theme[]>([]);
export const custom_theme_container = writable<HTMLElement | undefined>(undefined);
export const active_custom_theme = writable<Theme | undefined>(undefined);
export const custom_theme_audio_playing = writable<boolean>(false);
export const custom_theme_volume = writable<number>(0.15);
custom_theme_volume.subscribe((val) => {
if (browser) {
setGlobalVolume(val);
}
});
export const serverPing = writable<number | undefined>(undefined);
export const serverConnectionFails = writable(0);
+3 -1
View File
@@ -7,7 +7,9 @@ const osuSkinsCollator = new Intl.Collator(undefined, {
sensitivity: 'base',
});
export const sortSkins = (skins: { name: string, author: string | undefined, modified: number}[]) =>
export const sortSkins = (
skins: { name: string; author: string | undefined; modified: number }[]
) =>
skins.sort((a, b) => {
const pa = priority[a.name[0]];
const pb = priority[b.name[0]];
+411
View File
@@ -0,0 +1,411 @@
import * as path from '@tauri-apps/api/path';
import * as fs from '@tauri-apps/plugin-fs';
import { convertFileSrc } from '@tauri-apps/api/core';
import { active_custom_theme, custom_themes } from './global';
import { calculateGitBlobSha, setGlobalVolume } from './utils';
import { betterFetch } from '@better-fetch/fetch';
import zip from 'jszip';
import { SemVer } from 'semver';
export type Theme = {
folder_name: string;
name: string;
version: string;
author: string;
scriptUrl: string;
assets: string;
preview: string;
status: 'installed' | 'downloading' | 'extracting' | 'deleting' | 'not-installed';
progress: number;
updateAvailable: boolean;
};
export const getDownloadableThemes = async () => {
const themes: Theme[] = [];
const downloadableThemes = await betterFetch<
{
name: string;
version: string;
author: string;
}[]
>('https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/themes.json', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
},
});
if (downloadableThemes.data) {
for (const theme of downloadableThemes.data) {
themes.push({
folder_name: `${toSafeName(theme.name)}-${generateRandomString(16)}`,
name: theme.name,
version: theme.version,
author: theme.author,
scriptUrl: '',
assets: '',
preview: `https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/previews/${toSafeName(theme.name)}.png`,
status: 'not-installed',
progress: 0,
updateAvailable: false,
});
}
} else {
console.error('Failed to fetch downloadable themes');
console.error(downloadableThemes.error);
}
return themes;
};
export const getThemes = async (): Promise<Theme[]> => {
const themes: Theme[] = [];
themes.push({
folder_name: 'default',
author: 'EZPPFarm',
name: 'Default',
version: '1.0.0',
scriptUrl: '',
assets: '',
preview: '',
status: 'installed',
progress: 0,
updateAvailable: false,
});
const folderPath = await path.join(await path.homeDir(), '.ezpplauncher', 'themes');
if (await fs.exists(folderPath)) {
const themeFolders = await fs.readDir(folderPath);
for (const themeFolder of themeFolders) {
const themeFolderPath = await path.join(folderPath, themeFolder.name);
const themeConfigFilePath = await path.join(folderPath, themeFolder.name, 'theme.json');
if (await fs.exists(themeConfigFilePath)) {
const themeConfig = await fs.readTextFile(themeConfigFilePath);
const theme = JSON.parse(themeConfig) as {
name: string;
version: string;
apiVersion: string;
author: string;
entry: string;
style: string;
preview: string;
};
const scriptFile = await path.join(folderPath, themeFolder.name, theme.entry);
if (await fs.exists(scriptFile)) {
themes.push({
folder_name: themeFolder.name,
name: theme.name,
version: theme.version,
author: theme.author,
scriptUrl: convertFileSrc(await path.normalize(`${themeFolderPath}/${theme.entry}`)),
assets: convertFileSrc(await path.normalize(`${themeFolderPath}/assets`)),
preview: convertFileSrc(
await path.normalize(`${themeFolderPath}/assets/${theme.preview}`)
),
status: 'installed',
progress: 0,
updateAvailable: false,
});
}
}
}
}
return themes;
};
function combineChunks(chunks: Uint8Array[], totalLength: number): Uint8Array {
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
export const downloadTheme = async (
theme: Theme,
force = false
): Promise<{ success: boolean; error?: string }> => {
if (theme.status !== 'not-installed' && !force) return { success: false };
custom_themes.update((themes) => {
const matchedTheme = themes.find((t) => t.name === theme.name);
if (matchedTheme) {
matchedTheme.status = 'downloading';
}
return themes;
});
const themeFileName = `${toSafeName(theme.name)}.ezpplauncher-theme`;
const themeContents = await betterFetch<{
file_contents: {
sha: string;
download_url: string;
};
}>(
`https://git.ez-pp.farm/api/v1/repos/EZPPFarm/EZPPLauncher-Themes/contents-ext/themes/${themeFileName}?ref=main`
);
if (themeContents.error) {
custom_themes.update((themes) => {
const matchedTheme = themes.find((t) => t.name === theme.name);
if (matchedTheme) {
matchedTheme.status = 'not-installed';
}
return themes;
});
return {
success: false,
error: themeContents.error.message ?? 'An unknown error occurred',
};
}
const downloadUrl = `https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/themes/${themeFileName}`;
const downloadReq = await fetch(downloadUrl, {
method: 'GET',
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
},
});
if (!downloadReq.ok || downloadReq.body === null) {
custom_themes.update((themes) => {
const matchedTheme = themes.find((t) => t.name === theme.name);
if (matchedTheme) {
matchedTheme.status = 'not-installed';
}
return themes;
});
return {
success: false,
error: 'Failed to download theme file.',
};
}
const total = Number(downloadReq.headers.get('content-length')) || 0;
const reader = downloadReq.body.getReader();
let received = 0;
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
const progress = received / total;
custom_themes.update((themes) => {
const matchedTheme = themes.find((t) => t.name === theme.name);
if (matchedTheme) {
matchedTheme.progress = progress;
}
return themes;
});
}
const fileBuffer = combineChunks(chunks, total);
const sha = calculateGitBlobSha(Buffer.from(fileBuffer));
if (sha !== themeContents.data.file_contents.sha) {
custom_themes.update((themes) => {
const matchedTheme = themes.find((t) => t.name === theme.name);
if (matchedTheme) {
matchedTheme.status = 'not-installed';
}
return themes;
});
return {
success: false,
error: 'Failed to verify downloaded theme file hash.',
};
}
//TODO: check file hash
custom_themes.update((themes) => {
const matchedTheme = themes.find((t) => t.name === theme.name);
if (matchedTheme) {
matchedTheme.status = 'extracting';
}
return themes;
});
const baseThemeFolder = await path.join(await path.homeDir(), '.ezpplauncher', 'themes');
const themeFolder = await path.join(baseThemeFolder, toSafeName(theme.name));
if (!(await fs.exists(baseThemeFolder))) await fs.mkdir(baseThemeFolder, { recursive: true });
if (!(await fs.exists(themeFolder))) await fs.mkdir(themeFolder, { recursive: true });
const zipFile = await zip.loadAsync(fileBuffer);
const totalFiles = Object.keys(zipFile.files).length;
let extractedFiles = 0;
for (const zipEntry of Object.keys(zipFile.files)) {
const file = zipFile.file(zipEntry);
if (file) {
try {
const fileData = await file.async('uint8array');
const filePath = await path.join(themeFolder, zipEntry);
const dirPath = await path.dirname(filePath);
if (!(await fs.exists(dirPath))) await fs.mkdir(dirPath, { recursive: true });
await new Promise((res) => setTimeout(res, 250));
await fs.writeFile(filePath, fileData);
extractedFiles++;
custom_themes.update((themes) => {
const matchedTheme = themes.find((t) => t.name === theme.name);
if (matchedTheme) {
matchedTheme.progress = extractedFiles / totalFiles;
}
return themes;
});
} catch (err) {
console.log(err);
custom_themes.update((themes) => {
const matchedTheme = themes.find((t) => t.name === theme.name);
if (matchedTheme) {
matchedTheme.status = 'not-installed';
}
return themes;
});
return {
success: false,
error: 'Failed to extract theme file.',
};
}
}
}
const themeInfo = await fs.readTextFile(await path.join(themeFolder, 'theme.json'));
const themeInfoObj = JSON.parse(themeInfo) as {
name: string;
version: string;
apiVersion: string;
author: string;
entry: string;
style: string;
preview: string;
};
const themeScriptUrl = convertFileSrc(
await path.normalize(`${themeFolder}/${themeInfoObj.entry}`)
);
const themeAssets = convertFileSrc(await path.normalize(`${themeFolder}/assets`));
const themePreview = convertFileSrc(
await path.normalize(`${themeFolder}/assets/${themeInfoObj.preview}`)
);
const downloadableThemes = await getDownloadableThemes();
custom_themes.update((themes) => {
const reloadedThemes = [...themes];
for (const theme of downloadableThemes) {
if (!reloadedThemes.find((t) => t.name === theme.name)) reloadedThemes.push(theme);
const installedTheme = reloadedThemes.find((t) => t.name === theme.name);
if (installedTheme) {
const installedThemeVersion = new SemVer(installedTheme.version);
const downloadableThemeVersion = new SemVer(theme.version);
if (downloadableThemeVersion.compare(installedThemeVersion) > 0) {
installedTheme.updateAvailable = true;
const index = reloadedThemes.findIndex((t) => t.name === theme.name);
reloadedThemes[index] = installedTheme;
}
}
}
for (const installedThemes of reloadedThemes) {
if (installedThemes.name === theme.name) {
installedThemes.name = themeInfoObj.name;
installedThemes.author = themeInfoObj.author;
installedThemes.version = themeInfoObj.version;
installedThemes.scriptUrl = themeScriptUrl;
installedThemes.assets = themeAssets;
installedThemes.preview = themePreview;
installedThemes.status = 'installed';
}
}
return reloadedThemes.sort((a, b) => {
if (a.name === 'Default') return -1;
if (b.name === 'Default') return 1;
if (a.status === 'installed' && b.status !== 'installed') return -1;
if (a.status !== 'installed' && b.status === 'installed') return 1;
return a.name.localeCompare(b.name);
});
});
return {
success: true,
};
};
export const loadTheme = async (theme: Theme, themeContainer: HTMLElement, volume = 0.15) => {
active_custom_theme.set(theme);
themeContainer.innerHTML = '';
if (theme.scriptUrl.length > 0) {
/* @vite-ignore */
const themeScript = await import(theme.scriptUrl);
themeScript.mountTheme(themeContainer, { assets: theme.assets });
}
setGlobalVolume(volume);
};
export const deleteTheme = async (themeToUninstall: Theme) => {
if (themeToUninstall.status !== 'installed') return false;
const baseThemeFolder = await path.join(await path.homeDir(), '.ezpplauncher', 'themes');
if (!(await fs.exists(baseThemeFolder))) return false;
const themeFolder = await path.join(baseThemeFolder, themeToUninstall.folder_name);
if (!(await fs.exists(themeFolder))) return false;
custom_themes.update((themes) => {
const matchedTheme = themes.find((t) => t.name === themeToUninstall.name);
if (matchedTheme) {
matchedTheme.status = 'deleting';
}
return themes;
});
await fs.remove(themeFolder, { recursive: true });
const downloadableThemes = await getDownloadableThemes();
custom_themes.update((themes) => {
const reloadedThemes = [...themes];
for (const theme of downloadableThemes) {
if (theme.name === themeToUninstall.name) {
const themeIndex = reloadedThemes.findIndex((t) => t.name === theme.name);
if (themeIndex) {
reloadedThemes.splice(themeIndex, 1);
reloadedThemes.push(theme);
}
}
const installedTheme = reloadedThemes.find((t) => t.name === theme.name);
if (installedTheme) {
const installedThemeVersion = new SemVer(installedTheme.version);
const downloadableThemeVersion = new SemVer(theme.version);
if (downloadableThemeVersion.compare(installedThemeVersion) > 0) {
installedTheme.updateAvailable = true;
const index = reloadedThemes.findIndex((t) => t.name === theme.name);
reloadedThemes[index] = installedTheme;
}
}
}
return reloadedThemes.sort((a, b) => {
if (a.name === 'Default') return -1;
if (b.name === 'Default') return 1;
if (a.status === 'installed' && b.status !== 'installed') return -1;
if (a.status !== 'installed' && b.status === 'installed') return 1;
return a.name.localeCompare(b.name);
});
});
return true;
};
export const toSafeName = (name: string) => {
return (
name
.toLowerCase()
.replace(/[<>:"/\\|?*]/g, '_')
// eslint-disable-next-line no-control-regex
.replace(/[\x00-\x1F\x7F]/g, '_')
.replace(/[\s-]+/g, '_')
.replace(/_+/g, '_')
.replace(/[. ]+$/g, '_')
);
};
const generateRandomString = (length: number) => {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length }, () =>
characters.charAt(Math.floor(Math.random() * characters.length))
).join('');
};
+34
View File
@@ -0,0 +1,34 @@
declare global {
const umami: Umami;
}
/**
* @see {@link https://umami.is/docs/tracker-functions|Umami Docs}
*/
interface Umami {
track(): Promise<string> | undefined;
track(event_name: string, event_data?: Record<string, unknown>): Promise<string> | undefined;
track(custom_payload: { website: string; [key: string]: unknown }): Promise<string> | undefined;
track(
callback: (props: {
hostname: string;
language: string;
referrer: string;
screen: string;
title: string;
url: string;
website: string;
}) => { website: string; [key: string]: unknown }
): Promise<string> | undefined;
/** Pass in your own ID to identify a user. */
identify(identity_id: string): Promise<void>;
/** Save data about the current session. */
identify(identity_id: string, data: Record<string, unknown>): Promise<void>;
/** To save data without a unique ID, pass in only a JSON object. */
identify(data: Record<string, unknown>): Promise<void>;
}
export {};
+110
View File
@@ -1,6 +1,8 @@
import { invoke } from '@tauri-apps/api/core';
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { custom_theme_audio_playing } from './global';
import * as crypto from 'crypto-js';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -99,3 +101,111 @@ export const urlIsValidImage = async (url: string) => {
return false;
}
};
export function toURL(data: Buffer, type: string) {
return URL.createObjectURL(new Blob([new Uint8Array(data)], { type }));
}
export function setGlobalVolume(volume: number) {
const mediaElements: NodeListOf<HTMLMediaElement> = document.querySelectorAll('audio, video');
mediaElements.forEach((element) => {
element.volume = volume;
});
}
export function fadeGlobalVolume(
volumeFrom: number,
volumeTo: number,
duration: number,
onDone?: () => void
) {
const mediaElements: NodeListOf<HTMLMediaElement> = document.querySelectorAll('audio, video');
const startTime = performance.now();
function animate() {
const currentTime = performance.now();
const elapsedTime = currentTime - startTime;
const progress = Math.min(elapsedTime / duration, 1);
const interpolatedVolume = volumeFrom + (volumeTo - volumeFrom) * progress;
mediaElements.forEach((element) => {
element.volume = interpolatedVolume;
});
if (progress < 1) {
requestAnimationFrame(animate);
} else {
if (onDone) onDone();
}
}
animate();
}
export const pauseGlobalMedia = () => {
const mediaElements: NodeListOf<HTMLMediaElement> = document.querySelectorAll('audio, video');
mediaElements.forEach((element) => element.pause());
};
export const resumeGlobalMedia = () => {
const mediaElements: NodeListOf<HTMLMediaElement> = document.querySelectorAll('audio, video');
mediaElements.forEach((element) => element.play());
};
export const setCurrentTimeGlobalMedia = (time: number) => {
const mediaElements: NodeListOf<HTMLMediaElement> = document.querySelectorAll('audio, video');
mediaElements.forEach((element) => (element.currentTime = time));
};
export function trackMediaContainer(node: HTMLElement) {
const media = new Set<HTMLMediaElement>();
function update() {
custom_theme_audio_playing.set(Array.from(media).some((el) => !el.paused));
}
function register(el: HTMLMediaElement) {
media.add(el);
const handler = () => update();
el.addEventListener('play', handler);
el.addEventListener('pause', handler);
return () => {
media.delete(el);
el.removeEventListener('play', handler);
el.removeEventListener('pause', handler);
};
}
// find existing media inside the div
node.querySelectorAll<HTMLMediaElement>('video, audio').forEach(register);
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
m.addedNodes.forEach((n) => {
if (n instanceof HTMLMediaElement) register(n);
if (n instanceof HTMLElement) {
n.querySelectorAll<HTMLMediaElement>('video, audio').forEach(register);
}
});
}
});
observer.observe(node, { childList: true, subtree: true });
return {
destroy() {
observer.disconnect();
},
};
}
export function calculateGitBlobSha(fileBuffer: Buffer): string {
const header = Buffer.from(`blob ${fileBuffer.length}\0`);
const store = Buffer.concat([header, fileBuffer]);
return crypto.SHA1(crypto.lib.WordArray.create(store)).toString(crypto.enc.Hex);
}
+59 -127
View File
@@ -1,16 +1,18 @@
<script lang="ts">
import Logo from '$assets/logo.png';
import NyanCatSong from '$assets/nyan-cat.mp3';
import '../app.css';
import Titlebar from '@/components/ui/titlebar/titlebar.svelte';
import * as AlertDialog from '@/components/ui/alert-dialog';
import {
active_custom_theme,
currentLoadingInfo,
custom_theme_container,
custom_theme_volume,
custom_themes,
discordPresence,
firstStartup,
launcherVersion,
nyanCatSong,
platform,
presenceLoading,
setupValues,
@@ -29,21 +31,22 @@
import { Buffer } from 'buffer';
import SileoToast from '@/components/ui/sileo/sileo-toast.svelte';
import { userAuth } from '@/userAuthentication';
import { encryptString, exit, getLauncherVersion, getPlatform } from '@/osuUtil';
import { exit, getLauncherVersion, getPlatform } from '@/osuUtil';
import Button from '@/components/ui/button/button.svelte';
import * as presence from '@/presence';
import { fade } from 'svelte/transition';
import '@fontsource/sora';
import '@fontsource/space-mono';
import AnimatedBg from '@/components/ui/animated-bg/AnimatedBg.svelte';
import NyanCatBg from '@/components/ui/animated-bg/NyanCatBg.svelte';
import { getDownloadableThemes, getThemes, loadTheme } from '@/themes';
import { sileo } from 'sileo';
import AnimatedBg from '@/components/ui/animated-bg/AnimatedBg.svelte';
import { SemVer } from 'semver';
import { trackMediaContainer } from '@/utils';
let { children } = $props();
let unsupported_platform = $state<boolean>(false);
let secret_enabled = $state<boolean>(false);
function disableReload() {
if (window.location.hostname !== 'tauri.localhost') {
@@ -93,19 +96,6 @@
);
}
let konami_code = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'b',
'a',
];
onMount(async () => {
window.Buffer = Buffer;
@@ -115,109 +105,50 @@
platform.set(await getPlatform());
if ($platform !== 'windows' && $platform !== 'linux') unsupported_platform = true;
const isFirstStartup = await $userSettings.init();
firstStartup.set(isFirstStartup);
$userAuth.init();
document.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === konami_code[0].toLowerCase()) {
konami_code.shift();
if (konami_code.length === 0) {
if (secret_enabled) {
sileo.success({
title: 'Cheatcode deactivated!',
duration: 3000,
fill: '#181825',
styles: {
description: 'text-center!',
},
});
const audio = $nyanCatSong;
if (audio) {
const fadeOutDuration = 2000; // 2 seconds
const fadeOutSteps = 20;
const fadeOutStepTime = fadeOutDuration / fadeOutSteps;
const volumeStep = audio.volume / fadeOutSteps;
const fadeOutInterval = setInterval(() => {
if (audio.volume > 0) {
audio.volume = Math.max(0, audio.volume - volumeStep);
} else {
audio.pause();
clearInterval(fadeOutInterval);
}
}, fadeOutStepTime);
}
nyanCatSong.set(undefined);
secret_enabled = false;
konami_code = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'b',
'a',
];
return;
try {
const themeVolume = $userSettings.value('volume').get(0.15);
custom_theme_volume.set(themeVolume);
currentLoadingInfo.set('Loading themes...');
const downloadableThemes = await getDownloadableThemes();
const themes = await getThemes();
const combinedThemes = [...themes];
for (const theme of downloadableThemes) {
if (!combinedThemes.find((t) => t.name === theme.name)) combinedThemes.push(theme);
const installedTheme = themes.find((t) => t.name === theme.name);
if (installedTheme) {
const installedThemeVersion = new SemVer(installedTheme.version);
const downloadableThemeVersion = new SemVer(theme.version);
if (downloadableThemeVersion.compare(installedThemeVersion) > 0) {
installedTheme.updateAvailable = true;
const index = combinedThemes.findIndex((t) => t.name === theme.name);
combinedThemes[index] = installedTheme;
}
sileo.success({
title: 'Cheatcode activated!',
duration: 3000,
fill: '#181825',
styles: {
description: 'text-center!',
},
});
const audio = new Audio(NyanCatSong);
audio.loop = true;
audio.volume = 0;
audio.play();
const fadeInVolume = 0.15;
const fadeInDuration = 2000; // 2 seconds
const fadeInSteps = 20;
const fadeInStepTime = fadeInDuration / fadeInSteps;
const volumeStep = (fadeInVolume - audio.volume) / fadeInSteps;
const fadeInInterval = setInterval(() => {
if (audio.volume < fadeInVolume) {
audio.volume = Math.min(fadeInVolume, audio.volume + volumeStep);
} else {
clearInterval(fadeInInterval);
}
}, fadeInStepTime);
nyanCatSong.set(audio);
secret_enabled = true;
konami_code = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'b',
'a',
];
}
} else {
konami_code = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'b',
'a',
];
}
});
custom_themes.set(combinedThemes);
const activeTheme = $userSettings.value('theme').get('Default');
const theme = themes.find((theme) => theme.name === activeTheme);
if (!theme) {
$userSettings.value('theme').set('Default');
await $userSettings.save();
loadTheme(themes[0], $custom_theme_container!, themeVolume);
} else {
loadTheme(theme, $custom_theme_container!, themeVolume);
}
} catch {
sileo.error({
title: 'An error occured!',
description: 'Failed to load theme',
fill: '#181825',
styles: {
description: 'text-center!',
},
});
}
currentLoadingInfo.set('Loading config...');
const config_patching = $userSettings.value('patching');
@@ -263,8 +194,6 @@
presenceLoading.set(false);
}
} catch {}
firstStartup.set(isFirstStartup);
});
</script>
@@ -301,15 +230,18 @@
<main>
<div class="opacity-30">
{#if secret_enabled}
<div transition:fade={{ duration: $reduceAnimations ? 0 : 500 }}>
<NyanCatBg />
</div>
{:else}
<div transition:fade={{ duration: $reduceAnimations ? 0 : 500 }}>
<div transition:fade={{ duration: $reduceAnimations ? 0 : 500 }}>
<div
class={$active_custom_theme && $active_custom_theme.name === 'Default'
? 'hidden'
: 'absolute z-0 top-0 left-0 w-full h-full object-cover object-center aspect-video'}
bind:this={$custom_theme_container}
use:trackMediaContainer
></div>
{#if ($active_custom_theme && $active_custom_theme.name === 'Default') || !$active_custom_theme}
<AnimatedBg />
</div>
{/if}
{/if}
</div>
</div>
{@render children()}
</main>
+181 -43
View File
@@ -1,19 +1,23 @@
<script lang="ts">
import Logo from '$assets/logo.png';
import DefaultThemePreview from '$assets/default_preview.png';
import * as Avatar from '@/components/ui/avatar';
import Badge from '@/components/ui/badge/badge.svelte';
import Button from '@/components/ui/button/button.svelte';
import * as Select from '@/components/ui/select';
import {
active_custom_theme,
beatmapSets,
currentSkin,
custom_theme_container,
custom_theme_volume,
custom_themes,
discordPresence,
launcherStream,
launcherStreams,
launcherVersion,
launching,
newVersion,
nyanCatSong,
osuBuild,
osuStream,
platform,
@@ -39,16 +43,23 @@
ArrowRight,
Settings,
House,
Paintbrush,
Trash,
CloudDownload,
} from 'lucide-svelte';
import NumberFlow from '@number-flow/svelte';
import * as AlertDialog from '@/components/ui/alert-dialog';
import Progress from '@/components/ui/progress/progress.svelte';
import {
compareBuildNumbers,
fadeGlobalVolume,
formatBytes,
numberHumanReadable,
openURL,
pauseGlobalMedia,
releaseStreamToReadable,
resumeGlobalMedia,
setCurrentTimeGlobalMedia,
urlIsValidImage,
} from '@/utils';
import { fade, fly } from 'svelte/transition';
@@ -110,6 +121,8 @@
import DownloadButton from '@/components/ui/download-button/DownloadButton.svelte';
import { animate } from 'animejs';
import { sileo } from 'sileo';
import ScrollContainer from '@/components/ui/scroll-container/ScrollContainer.svelte';
import { deleteTheme, downloadTheme, loadTheme } from '@/themes';
let selectedView = $state('home');
let progress = $state(-1);
@@ -435,23 +448,7 @@
await new Promise((res) => setTimeout(res, 1500));
launchInfo = 'Launching osu!...';
if ($nyanCatSong) {
const audio = $nyanCatSong;
const fadeOutDuration = 2000; // 2 seconds
const fadeOutSteps = 20;
const fadeOutStepTime = fadeOutDuration / fadeOutSteps;
const volumeStep = audio.volume / fadeOutSteps;
const fadeOutInterval = setInterval(() => {
if (audio.volume > 0) {
audio.volume = Math.max(0, audio.volume - volumeStep);
} else {
clearInterval(fadeOutInterval);
audio.pause();
audio.currentTime = 0;
}
}, fadeOutStepTime);
}
fadeGlobalVolume($custom_theme_volume, 0, 2000, pauseGlobalMedia);
await replaceUIFiles(osuPath, false);
await new Promise((res) => setTimeout(res, 1000));
@@ -563,25 +560,9 @@
cleanup = true;
launchInfo = 'Cleaning up...';
if ($nyanCatSong) {
const audio = $nyanCatSong;
audio.play();
audio.volume = 0;
const fadeInVolume = 0.15;
const fadeInDuration = 2000; // 2 seconds
const fadeInSteps = 20;
const fadeInStepTime = fadeInDuration / fadeInSteps;
const volumeStep = (fadeInVolume - audio.volume) / fadeInSteps;
const fadeInInterval = setInterval(() => {
if (audio.volume < fadeInVolume) {
audio.volume = Math.min(fadeInVolume, audio.volume + volumeStep);
} else {
clearInterval(fadeInInterval);
}
}, fadeInStepTime);
}
setCurrentTimeGlobalMedia(0);
resumeGlobalMedia();
fadeGlobalVolume(0, $custom_theme_volume, 2000);
await getCurrentWindow().show();
if (presenceUpdater) {
@@ -925,15 +906,15 @@
<div
class="p-3 border-r border-r-theme-900 flex flex-col items-center gap-2 z-10 bg-black/40 backdrop-blur-sm"
>
<div class="bg-primary-800/80 ring-1 ring-inset ring-white/15 rounded-[1.1rem] p-1.5">
<div class="bg-primary/30 ring-1 ring-inset ring-white/15 rounded-[1.1rem] p-1.5">
<img class="pointer-events-none" src={Logo} alt="logo" bind:this={ezppLogo} />
</div>
<Badge class="text-[0.5rem] py-0 px-2">{$launcherVersion || 'dev'}</Badge>
<Button
class="flex size-12 items-center gap-2 ring-1 ring-inset ring-white/15 {selectedView ===
'home'
? 'bg-primary-300/50'
: 'bg-black/20 border-black/20'} hover:bg-primary-300/50 rounded-[0.85rem] p-3 mt-3"
? 'bg-primary/50'
: 'bg-black/20 border-black/20'} hover:bg-primary/50 rounded-[0.85rem] p-3 mt-3"
disabled={$launching}
onclick={() => {
if (!$launching) selectedView = 'home';
@@ -941,11 +922,23 @@
>
<House class="text-theme-200 !size-5" />
</Button>
<Button
class="flex size-12 items-center gap-2 ring-1 ring-inset ring-white/15 {selectedView ===
'themes'
? 'bg-primary/50'
: 'bg-black/20 border-black/20'} hover:bg-primary/50 rounded-[0.85rem] p-3 mt-3"
disabled={$launching}
onclick={() => {
if (!$launching) selectedView = 'themes';
}}
>
<Paintbrush class="text-theme-200 !size-5" />
</Button>
<Button
class="flex size-12 items-center gap-2 ring-1 ring-inset ring-white/15 {selectedView ===
'settings'
? 'bg-primary-300/50'
: 'bg-black/20 border-black/20'} hover:bg-primary-300/50 rounded-[0.85rem] p-3 mt-3"
? 'bg-primary/50'
: 'bg-black/20 border-black/20'} hover:bg-primary/50 rounded-[0.85rem] p-3 mt-3"
disabled={$launching}
onclick={() => {
if (!$launching) selectedView = 'settings';
@@ -1009,7 +1002,9 @@
}}
out:fly={{ duration: $reduceAnimations ? 0 : 400, y: -5, opacity: 0 }}
>
<div class="relative z-10 px-8 py-4 flex items-center justify-between">
<div
class="relative z-10 px-8 py-4 flex items-center justify-between bg-black/40 backdrop-blur-sm border-t border-t-theme-900"
>
<div class="flex items-center gap-4">
{#if $osuInstallationPath !== ''}
<div class="flex items-center gap-2">
@@ -1532,6 +1527,149 @@
</Button>
</form>
</div>
{:else if selectedView === 'themes'}
<div
class="h-[100vh] w-full flex flex-col items-center bg-black/20 backdrop-blur-sm"
in:fly={{
duration: $reduceAnimations ? 0 : 400,
delay: $reduceAnimations ? 0 : 400,
y: 5,
opacity: 0,
}}
out:fly={{ duration: $reduceAnimations ? 0 : 400, y: -5, opacity: 0 }}
>
<ScrollContainer class="pt-8" topOffset={45}>
<div class="grid w-full gap-1 grid-cols-3 p-3 pr-5">
{#each $custom_themes as theme (theme.folder_name)}
<div
class="group overflow-hidden rounded-3xl border {$active_custom_theme &&
$active_custom_theme.folder_name === theme.folder_name
? 'border-primary'
: 'border-theme-800'} bg-theme-950 transition hover:border-white/20 h-[295px]"
>
<img
src={theme.preview || DefaultThemePreview}
alt="Preview of {theme.name}"
class="mb-2 h-40 w-full rounded-t-3xl object-cover object-center pointer-events-none select-none"
/>
<div class="flex flex-col gap-1 p-3">
<div class="mb-4 flex items-center justify-between gap-2">
<div class="flex flex-col items-start">
<span class="text-sm font-semibold">{theme.name}</span>
<span class="text-xs text-muted-foreground">by {theme.author}</span>
</div>
<span class="text-xs text-muted-foreground">v{theme.version}</span>
</div>
<div class="flex items-center gap-1">
<Button
class="w-full"
disabled={($active_custom_theme &&
$active_custom_theme.folder_name === theme.folder_name) ||
theme.status === 'downloading' ||
theme.status === 'extracting' ||
theme.status === 'deleting'}
onclick={async () => {
if (theme.status === 'installed') {
if ($custom_theme_container) {
loadTheme(theme, $custom_theme_container, $custom_theme_volume);
$userSettings.value('theme').set(theme.name);
$userSettings.save();
} else {
sileo.error({
title: 'Uhhm..',
description: 'Failed to apply theme.',
});
}
} else {
const downloadThemeResult = await downloadTheme(theme);
if (!downloadThemeResult.success) {
if (downloadThemeResult.error && downloadThemeResult.error.length > 0) {
sileo.error({
title: 'Uhhm..',
description: downloadThemeResult.error,
fill: '#181825',
styles: {
description: 'text-center!',
},
});
}
} else {
sileo.success({
title: 'Yaay!',
description: 'Theme installed successfully!',
fill: '#181825',
styles: {
description: 'text-center!',
},
});
}
}
}}
>
{#if $active_custom_theme && $active_custom_theme.folder_name === theme.folder_name}
Theme in use
{:else if theme.status !== 'installed'}
{#if theme.status === 'downloading'}
{theme.updateAvailable ? 'Updating' : 'Downloading'} Theme... ({Math.round(
theme.progress * 100
)}%)
{:else if theme.status === 'extracting'}
Extracting Theme... ({Math.round(theme.progress * 100)}%)
{:else if theme.status === 'deleting'}
Uninstalling Theme...
{:else}
Download Theme
{/if}
{:else}
Use Theme
{/if}
</Button>
{#if theme.status === 'installed' && theme.updateAvailable}
<Button
class="min-w-[40px]"
size="icon"
variant="secondary"
onclick={async () => {
const defaultTheme = $custom_themes.find((t) => t.name === 'Default');
if (defaultTheme) {
loadTheme(defaultTheme, $custom_theme_container!, $custom_theme_volume);
$userSettings.value('theme').set(defaultTheme.name);
$userSettings.save();
if (!(await downloadTheme(theme, true))) {
sileo.error({
title: 'Uhhm..',
description: 'Failed to update theme.',
});
}
}
}}
>
<CloudDownload />
</Button>
{/if}
{#if (theme.status === 'installed' || theme.status === 'deleting') && $active_custom_theme && $active_custom_theme.folder_name !== theme.folder_name && theme.name !== 'Default'}
<Button
class="min-w-[40px]"
size="icon"
variant="destructive"
disabled={theme.status !== 'installed'}
onclick={() => deleteTheme(theme)}
>
{#if theme.status === 'deleting'}
<LoaderCircle class="animate-spin" />
{:else}
<Trash />
{/if}
</Button>
{/if}
</div>
</div>
</div>
{/each}
</div>
</ScrollContainer>
</div>
{/if}
</div>
</div>
+7 -5
View File
@@ -131,6 +131,9 @@
}
if (!$firstStartup) {
/* const launcherTheme = $userSettings.value('launcherTheme').get('default');
const them = THEMES.find((theme) => theme.name === launcherTheme);
if (them) theme.set(them); */
currentLoadingInfo.set('Checking osu installation path...');
const validFolder = await isValidOsuFolder($osuInstallationPath);
if (!validFolder) {
@@ -158,7 +161,6 @@
currentLoadingInfo.set('Counting skins...');
const skins_list = await getSkins($osuInstallationPath);
console.log(skins_list);
if (skins_list) {
skins.set(skins_list);
skinsCount.set(skins_list.length);
@@ -252,19 +254,19 @@
});
</script>
<div class="flex flex-col items-center justify-center h-[100vh] w-full">
<div class="relative w-80 h-80 flex items-center justify-center">
<div class="flex flex-col items-center justify-center h-[100vh] w-full z-10">
<div class="relative w-80 h-80 flex items-center justify-center z-10">
<svg
class="absolute top-0 left-0 w-full h-full animate-spin"
style="animation-duration: 5s;"
viewBox="0 0 208 208"
>
<circle
class="stroke-primary"
cx="104"
cy="104"
r="90"
fill="none"
stroke="#ff0098"
stroke-width="8"
stroke-linecap="round"
stroke-dasharray="180 385"
@@ -279,5 +281,5 @@
bind:this={ezppLogo}
/>
</div>
<span class="text-theme-200 text-sm mt-5">{$currentLoadingInfo}</span>
<span class="text-theme-200 text-sm mt-5 z-10">{$currentLoadingInfo}</span>
</div>
-6
View File
@@ -124,7 +124,6 @@
onMount(async () => {
const osuPath = await autoDetectOsuInstallFolder();
console.log(osuPath);
if (osuPath) {
osuInstallPath = osuPath;
autoDetectedOsuPath = true;
@@ -280,7 +279,6 @@
disabled={$platform !== 'windows'}
onCheckedChange={async (e) => {
patch.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
@@ -299,8 +297,6 @@
cursorSmoothening.set(false);
}
customCursor.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
@@ -317,7 +313,6 @@
onCheckedChange={async (e) => {
if (!$customCursor) return;
cursorSmoothening.set(e);
$userSettings.save();
}}
disabled={!$customCursor}
class="flex items-center justify-center w-5 h-5"
@@ -335,7 +330,6 @@
checked={$reduceAnimations}
onCheckedChange={async (e) => {
reduceAnimations.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>