28 Commits

Author SHA1 Message Date
cdc3c78ffe fix: skin count loader 2025-08-05 22:14:54 +02:00
509aa36379 chore: change easing method of tab crossfade 2025-08-05 22:12:22 +02:00
20e5961b7c chore: remove old tab selector 2025-08-05 22:07:23 +02:00
9b816e1e5c chore: add crossfade to tab selector 2025-08-05 22:06:40 +02:00
ada83b32dc chore: update deps 2025-08-05 21:51:24 +02:00
cbf85753a8 fix: tailwind dark mode variant selector 2025-08-05 21:51:13 +02:00
eb1ef06c07 chore: add check and install for dotnet8 on preinstall 2025-08-05 07:24:46 +02:00
a64655ec5d Merge branch 'master' of https://git.ez-pp.farm/EZPPFarm/EZPPLauncher 2025-07-31 15:36:06 +02:00
45b902cfb6 chore: update dependencies 2025-07-31 15:36:04 +02:00
f53aba9a7b fix: add CREATE_NO_WINDOW flag to command creation 2025-07-31 15:35:47 +02:00
f7c2dc689a fix: update silent argument for nsis 2025-07-30 21:35:44 +02:00
b778a5a369 chore: bump version 2025-07-30 20:27:02 +02:00
084e006f06 chore: add inline updater 2025-07-30 20:26:05 +02:00
9b3cb0bd26 fix: remove duplicate key 2025-07-30 19:13:07 +02:00
c5649c3f8a Merge branch 'master' of https://git.ez-pp.farm/EZPPFarm/EZPPLauncher 2025-07-30 19:07:14 +02:00
fac54f3e59 fix: switch to nsis installer 2025-07-30 19:04:21 +02:00
b6f7f84955 fix: overflow issue on error dialog 2025-07-24 08:55:08 +02:00
aa52ff3b8c chore: bump version 2025-07-24 08:42:41 +02:00
d72d2731d9 chore: remove unneeded component and tauri app register 2025-07-24 08:42:14 +02:00
f3ac7cc684 chore: update tauri and tauri-apps 2025-07-24 08:41:47 +02:00
b1b3de295c chore: remove unneeded modules 2025-07-24 08:41:30 +02:00
e76affaea1 fix: force stable release stream 2025-07-24 08:31:37 +02:00
700274d7d8 fix: invalid responses from osu api 2025-07-24 08:31:26 +02:00
53b6c2e174 Merge branch 'master' of https://git.ez-pp.farm/EZPPFarm/EZPPLauncher 2025-07-23 12:06:39 +02:00
93ecea9083 fix: button typo 2025-07-23 12:06:33 +02:00
2549b18e8c fix: skins count display when no skins are in the skins directory 2025-07-21 22:54:21 +02:00
fd4d911075 chore: add vendor info, bump version 2025-07-17 09:03:01 +02:00
5edd53eead chore: update frontend modules 2025-07-17 09:02:20 +02:00
17 changed files with 936 additions and 1057 deletions

View File

@@ -7,21 +7,17 @@
"@better-fetch/fetch": "^1.1.18", "@better-fetch/fetch": "^1.1.18",
"@fontsource/sora": "^5.2.6", "@fontsource/sora": "^5.2.6",
"@fontsource/space-mono": "^5.2.8", "@fontsource/space-mono": "^5.2.8",
"@iarna/toml": "2.2.5",
"@number-flow/svelte": "^0.3.9", "@number-flow/svelte": "^0.3.9",
"@tailwindcss/typography": "0.5.16", "@tailwindcss/typography": "0.5.16",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@tauri-apps/api": "2.6.0", "@tauri-apps/api": "2.6.0",
"@tauri-apps/plugin-dialog": "2.3.0", "@tauri-apps/plugin-dialog": "2.3.2",
"@tauri-apps/plugin-fs": "2.4.0", "@tauri-apps/plugin-fs": "2.4.1",
"@tauri-apps/plugin-shell": "2.3.0", "@tauri-apps/plugin-shell": "2.3.0",
"@tauri-apps/plugin-sql": "2.3.0",
"animejs": "^4.0.2", "animejs": "^4.0.2",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"lucide-svelte": "0.525.0", "lucide-svelte": "^0.536.0",
"osu-classes": "3.1.0",
"osu-parsers": "4.1.7",
"semver": "^7.7.2", "semver": "^7.7.2",
"svelte-confetti": "^2.0.0", "svelte-confetti": "^2.0.0",
"tw-animate-css": "^1.3.0", "tw-animate-css": "^1.3.0",
@@ -29,10 +25,11 @@
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.2.5", "@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0", "@eslint/js": "^9.18.0",
"@lucide/svelte": "^0.525.0", "@iarna/toml": "2.2.5",
"@lucide/svelte": "^0.536.0",
"@sveltejs/adapter-static": "3.0.8", "@sveltejs/adapter-static": "3.0.8",
"@sveltejs/kit": "2.22.2", "@sveltejs/kit": "2.22.2",
"@sveltejs/vite-plugin-svelte": "5.1.0", "@sveltejs/vite-plugin-svelte": "^6.1.0",
"@tauri-apps/cli": "2.6.1", "@tauri-apps/cli": "2.6.1",
"@types/bun": "^1.2.18", "@types/bun": "^1.2.18",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
@@ -52,14 +49,14 @@
"svelte-check": "4.2.2", "svelte-check": "4.2.2",
"svelte-sonner": "^1.0.5", "svelte-sonner": "^1.0.5",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwind-variants": "^1.0.0", "tailwind-variants": "^2.1.0",
"tailwindcss": "^4.1.7", "tailwindcss": "^4.1.7",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"tslib": "2.8.1", "tslib": "2.8.1",
"typescript": "5.8.3", "typescript": "5.8.3",
"typescript-eslint": "^8.20.0", "typescript-eslint": "^8.20.0",
"vite": "7.0.0", "vite": "7.0.0",
"vite-plugin-devtools-json": "^0.2.0", "vite-plugin-devtools-json": "^0.4.1",
}, },
}, },
}, },
@@ -174,7 +171,7 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@lucide/svelte": ["@lucide/svelte@0.525.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-dyUxkXzepagLUzL8jHQNdeH286nC66ClLACsg+Neu/bjkRJWPWMzkT+H0DKlE70QdkicGCfs1ZGmXCc351hmZA=="], "@lucide/svelte": ["@lucide/svelte@0.536.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-YAeoWU+0B/RriFZZ3wHno1FMkbrVrFdityuo2B0YuphD0vtJWXStzZkWLGVhT3jMb7zhugmhayIg+gI4+AZu1g=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
@@ -232,9 +229,9 @@
"@sveltejs/kit": ["@sveltejs/kit@2.22.2", "", { "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0", "vitefu": "^1.0.6" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-2MvEpSYabUrsJAoq5qCOBGAlkICjfjunrnLcx3YAk2XV7TvAIhomlKsAgR4H/4uns5rAfYmj7Wet5KRtc8dPIg=="], "@sveltejs/kit": ["@sveltejs/kit@2.22.2", "", { "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0", "vitefu": "^1.0.6" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-2MvEpSYabUrsJAoq5qCOBGAlkICjfjunrnLcx3YAk2XV7TvAIhomlKsAgR4H/4uns5rAfYmj7Wet5KRtc8dPIg=="],
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.1.0", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", "vitefu": "^1.0.6" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-wojIS/7GYnJDYIg1higWj2ROA6sSRWvcR1PO/bqEyFr/5UZah26c8Cz4u0NaqjPeVltzsVpt2Tm8d2io0V+4Tw=="], "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.1.0", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0-next.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-+U6lz1wvGEG/BvQyL4z/flyNdQ9xDNv5vrh+vWBWTHaebqT0c9RNggpZTo/XSPoHsSCWBlYaTlRX8pZ9GATXCw=="],
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="], "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.0", "", { "dependencies": { "debug": "^4.4.1" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-iwQ8Z4ET6ZFSt/gC+tVfcsSBHwsqc6RumSaiLUkAurW3BCpJam65cmHw0oOlDMTO0u+PZi9hilBRYN+LZNHTUQ=="],
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="], "@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
@@ -296,14 +293,12 @@
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.6.1", "", { "os": "win32", "cpu": "x64" }, "sha512-fBsjPqIIHaaQt7tnjIGmPHu5p/BNBVD4JfOhO3QqIVBzAb+W2bDyIQPdoDMI943soLr/+N10xeTiPu+3L74+dQ=="], "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.6.1", "", { "os": "win32", "cpu": "x64" }, "sha512-fBsjPqIIHaaQt7tnjIGmPHu5p/BNBVD4JfOhO3QqIVBzAb+W2bDyIQPdoDMI943soLr/+N10xeTiPu+3L74+dQ=="],
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.3.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-ylSBvYYShpGlKKh732ZuaHyJ5Ie1JR71QCXewCtsRLqGdc8Is4xWdz6t43rzXyvkItM9syNPMvFVcvjgEy+/GA=="], "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-cNLo9YeQSC0MF4IgXnotHsqEgJk72MBZLXmQPrLA95qTaaWiiaFQ38hIMdZ6YbGUNkr3oni3EhU+AD5jLHcdUA=="],
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-Sp8AdDcbyXyk6LD6Pmdx44SH3LPeNAvxR2TFfq/8CwqzfO1yOyV+RzT8fov0NNN7d9nvW7O7MtMAptJ42YXA5g=="], "@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-vJlKZVGF3UAFGoIEVT6Oq5L4HGDCD78WmA4uhzitToqYiBKWAvZR61M6zAyQzHqLs0ADemkE4RSy/5sCmZm6ZQ=="],
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-6GIRxO2z64uxPX4CCTuhQzefvCC0ew7HjdBhMALiGw74vFBDY95VWueAHOHgNOMV4UOUAFupyidN9YulTe5xlA=="], "@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-6GIRxO2z64uxPX4CCTuhQzefvCC0ew7HjdBhMALiGw74vFBDY95VWueAHOHgNOMV4UOUAFupyidN9YulTe5xlA=="],
"@tauri-apps/plugin-sql": ["@tauri-apps/plugin-sql@2.3.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-JYwIocfsLaDWa41LMiZWuzts7yCJR+EpZPRmgpO7Gd7XiAS9S67dKz306j/k/d9XntB0YopMRBol2OIWMschuA=="],
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
@@ -560,9 +555,7 @@
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"lucide-svelte": ["lucide-svelte@0.525.0", "", { "peerDependencies": { "svelte": "^3 || ^4 || ^5.0.0-next.42" } }, "sha512-kfuN6JcCqTfCz2B76aXnyGLAzEBRSYw5GaUspM5RNHQZS5aI5yaKu06fbaofOk8cDvUtY0AUm/zAix7aUX6Q3A=="], "lucide-svelte": ["lucide-svelte@0.536.0", "", { "peerDependencies": { "svelte": "^3 || ^4 || ^5.0.0-next.42" } }, "sha512-UIm/R2IYZMs4HW134ik7rDqEDXdvXb109Fznb8mG7OFQShnXkRpAaoyytJ8FzXR5W6GbfQ+r5jHBnIUs6uQp9w=="],
"lzma-js-simple-v2": ["lzma-js-simple-v2@1.2.3", "", { "dependencies": { "@types/node": "^20.12.7" } }, "sha512-6kgy86Q3YLolV6dOwCqdQXg3V07e3XJJ6wqfrN8/s65mvCfqkr+jMJkfiSZNvk+u2ig+G8rLdtaoW/g1oJiwow=="],
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
@@ -598,10 +591,6 @@
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"osu-classes": ["osu-classes@3.1.0", "", {}, "sha512-kz38FWMGnz5lr6ofovUrNaDAcs1gNpwlDc1qHjW86ILQZXG44w/+NflV7EyFomkI05XCADGfltE4FVoPwrkrmg=="],
"osu-parsers": ["osu-parsers@4.1.7", "", { "dependencies": { "lzma-js-simple-v2": "^1.2.3" }, "peerDependencies": { "osu-classes": "^3.1.0" } }, "sha512-b8aYJy9vK0Yk8zVbxVN+HSChGnXufSxePb3gABG8s5YGCY+31CKJTUHDAVRST0kX4lyeI4Z3iRxsCRftHFHEug=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
@@ -726,7 +715,7 @@
"tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="], "tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
"tailwind-variants": ["tailwind-variants@1.0.0", "", { "dependencies": { "tailwind-merge": "3.0.2" }, "peerDependencies": { "tailwindcss": "*" } }, "sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA=="], "tailwind-variants": ["tailwind-variants@2.1.0", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-82m0eRex0z6A3GpvfoTCpHr+wWJmbecfVZfP3mqLoDxeya5tN4mYJQZwa5Aw1hRZTedwpu1D2JizYenoEdyD8w=="],
"tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="], "tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="],
@@ -768,7 +757,7 @@
"vite": ["vite@7.0.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g=="], "vite": ["vite@7.0.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g=="],
"vite-plugin-devtools-json": ["vite-plugin-devtools-json@0.2.0", "", { "dependencies": { "uuid": "^11.1.0" }, "peerDependencies": { "vite": "^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "sha512-K7PoaWOEJECZ1n3VbhJXsUAX2PsO0xY7KFMM/Leh7tUev0M5zi+lz+vnVVdCK17IOK9Jp9rdzHXc08cnQirGbg=="], "vite-plugin-devtools-json": ["vite-plugin-devtools-json@0.4.1", "", { "dependencies": { "uuid": "^11.1.0" }, "peerDependencies": { "vite": "^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-pN+QJL+NwZUV+Via8w/Sh6X2pDrVClIMDAXdl7+EteXKB6mcHhsFGGclmxrPx6ZPGKSK5ez5ns64oRpjE5wFCg=="],
"vitefu": ["vitefu@1.0.7", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-eRWXLBbJjW3X5z5P5IHcSm2yYbYRPb2kQuc+oqsbAl99WB5kVsPbiiox+cymo8twTzifA6itvhr2CmjnaZZp0Q=="], "vitefu": ["vitefu@1.0.7", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-eRWXLBbJjW3X5z5P5IHcSm2yYbYRPb2kQuc+oqsbAl99WB5kVsPbiiox+cymo8twTzifA6itvhr2CmjnaZZp0Q=="],
@@ -792,6 +781,8 @@
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
"@sveltejs/vite-plugin-svelte/vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
"@tailwindcss/node/jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], "@tailwindcss/node/jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
@@ -826,8 +817,6 @@
"svelte-sonner/runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="], "svelte-sonner/runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="],
"tailwind-variants/tailwind-merge": ["tailwind-merge@3.0.2", "", {}, "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"mode-watcher/svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], "mode-watcher/svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],

View File

@@ -21,21 +21,17 @@
"@better-fetch/fetch": "^1.1.18", "@better-fetch/fetch": "^1.1.18",
"@fontsource/sora": "^5.2.6", "@fontsource/sora": "^5.2.6",
"@fontsource/space-mono": "^5.2.8", "@fontsource/space-mono": "^5.2.8",
"@iarna/toml": "2.2.5",
"@number-flow/svelte": "^0.3.9", "@number-flow/svelte": "^0.3.9",
"@tailwindcss/typography": "0.5.16", "@tailwindcss/typography": "0.5.16",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@tauri-apps/api": "2.6.0", "@tauri-apps/api": "2.6.0",
"@tauri-apps/plugin-dialog": "2.3.0", "@tauri-apps/plugin-dialog": "2.3.2",
"@tauri-apps/plugin-fs": "2.4.0", "@tauri-apps/plugin-fs": "2.4.1",
"@tauri-apps/plugin-shell": "2.3.0", "@tauri-apps/plugin-shell": "2.3.0",
"@tauri-apps/plugin-sql": "2.3.0",
"animejs": "^4.0.2", "animejs": "^4.0.2",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"lucide-svelte": "0.525.0", "lucide-svelte": "^0.536.0",
"osu-classes": "3.1.0",
"osu-parsers": "4.1.7",
"semver": "^7.7.2", "semver": "^7.7.2",
"tw-animate-css": "^1.3.0", "tw-animate-css": "^1.3.0",
"svelte-confetti": "^2.0.0" "svelte-confetti": "^2.0.0"
@@ -43,10 +39,11 @@
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.2.5", "@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0", "@eslint/js": "^9.18.0",
"@lucide/svelte": "^0.525.0", "@iarna/toml": "2.2.5",
"@lucide/svelte": "^0.536.0",
"@sveltejs/adapter-static": "3.0.8", "@sveltejs/adapter-static": "3.0.8",
"@sveltejs/kit": "2.22.2", "@sveltejs/kit": "2.22.2",
"@sveltejs/vite-plugin-svelte": "5.1.0", "@sveltejs/vite-plugin-svelte": "^6.1.0",
"@tauri-apps/cli": "2.6.1", "@tauri-apps/cli": "2.6.1",
"@types/bun": "^1.2.18", "@types/bun": "^1.2.18",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
@@ -66,13 +63,13 @@
"svelte-check": "4.2.2", "svelte-check": "4.2.2",
"svelte-sonner": "^1.0.5", "svelte-sonner": "^1.0.5",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwind-variants": "^1.0.0", "tailwind-variants": "^2.1.0",
"tailwindcss": "^4.1.7", "tailwindcss": "^4.1.7",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"tslib": "2.8.1", "tslib": "2.8.1",
"typescript": "5.8.3", "typescript": "5.8.3",
"typescript-eslint": "^8.20.0", "typescript-eslint": "^8.20.0",
"vite": "7.0.0", "vite": "7.0.0",
"vite-plugin-devtools-json": "^0.2.0" "vite-plugin-devtools-json": "^0.4.1"
} }
} }

1085
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ezpplauncher" name = "ezpplauncher"
version = "3.0.1" version = "3.0.4"
description = "EZPPLauncher redefined." description = "EZPPLauncher redefined."
authors = ["HorizonCode"] authors = ["HorizonCode"]
edition = "2024" edition = "2024"
@@ -15,23 +15,22 @@ name = "ezpplauncher_lib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.3.0", features = [] } tauri-build = { version = "2.3.1", features = [] }
[dependencies] [dependencies]
tauri = { version = "2.6.2", features = [] } tauri = { version = "2.7.0", features = [] }
tauri-plugin-shell = "2.3.0" tauri-plugin-shell = "2.3.0"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140" serde_json = "1.0.141"
serde_repr = "0.1.20" serde_repr = "0.1.20"
tauri-plugin-sql = "2.3.0" tauri-plugin-dialog = "2.3.2"
tauri-plugin-dialog = "2.3.0" tauri-plugin-fs = "2.4.1"
tauri-plugin-fs = "2.4.0"
hardware-id = "0.3.0" hardware-id = "0.3.0"
tauri-plugin-cors-fetch = "4.1.0" tauri-plugin-cors-fetch = "4.1.0"
sysinfo = "0.35.2" sysinfo = "0.36.1"
reqwest = { version = "0.12.22", features = ["json", "stream"] } reqwest = { version = "0.12.22", features = ["json", "stream"] }
md5 = "0.8.0" md5 = "0.8.0"
tokio = { version = "1.46.1", features = ["full"] } tokio = { version = "1.47.0", features = ["full"] }
open = "5.3.2" open = "5.3.2"
windows-sys = "0.60.2" windows-sys = "0.60.2"
discord-rich-presence = "0.2.5" discord-rich-presence = "0.2.5"
@@ -40,9 +39,9 @@ once_cell = "1.21.3"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = "0.55.0" winreg = "0.55.0"
winapi = { version = "0.3", features = ["winuser", "wincrypt", "memoryapi", "winbase", "dpapi"] } winapi = { version = "0.3", features = ["winuser", "wincrypt", "memoryapi", "winbase", "dpapi"] }
base64 = "0.21" base64 = "0.22.1"
widestring = "1.0" widestring = "1.0"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-single-instance = "2.3.0" tauri-plugin-single-instance = "2.3.2"

343
src-tauri/nsis/dotnet.nsh Normal file
View File

@@ -0,0 +1,343 @@
; A set of NSIS macros to check whether a dotnet core runtime is installed and, if not, offer to
; download and install it. Supports dotnet versions 3.1 and newer - latest tested version is 7.0.
;
; Inspired by & initially based on NsisDotNetChecker, which does the same thing for .NET framework
; https://github.com/alex-sitnikov/NsisDotNetChecker
!include "WordFunc.nsh"
!include "TextFunc.nsh"
!include "X64.nsh"
!ifndef DOTNETCORE_INCLUDED
!define DOTNETCORE_INCLUDED
; Check that a specific version of the dotnet core runtime is installed and, if not, attempts to
; install it
;
; \param Version The desired dotnet core runtime version as a 2 digit version. e.g. 3.1, 6.0, 7.0
!macro CheckDotNetCore Version
; Save registers
Push $R0
Push $R1
Push $R2
; Push and pop parameters so we don't have conflicts if parameters are $R#
Push ${Version}
Pop $R0 ; Version
!define ID ${__LINE__}
; Check current installed version
!insertmacro DotNetCoreGetInstalledVersion $R0 $R1
; If $R1 is blank then there is no version installed, otherwise it is installed
; todo in future we might want to support "must be at least 6.0.7", for now we only deal with "yes/no" for a major version (e.g. 6.0)
StrCmp $R1 "" notinstalled_${ID}
DetailPrint "dotnet version $R1 already installed"
Goto end_${ID}
notinstalled_${ID}:
DetailPrint "dotnet $R0 is not installed"
!insertmacro DotNetCoreGetLatestVersion $R0 $R1
DetailPrint "Latest Version of $R0 is $R1"
; Get number of input digits
; ${WordFind} $R1 "." "#" $R2
; DetailPrint "version parts count is $R2"
; ${WordFind} $R1 "." "+1" $R2
; DetailPrint "version part 1 is $R2"
; ${WordFind} $R1 "." "+2" $R2
; DetailPrint "version part 2 is $R2"
; ${WordFind} $R1 "." "+3" $R2
; DetailPrint "version part 3 is $R2"
!insertmacro DotNetCoreInstallVersion $R1
end_${ID}:
!undef ID
; Restore registers
Pop $R2
Pop $R1
Pop $R0
!macroend
; Gets the latest version of the runtime for a specified dotnet version. This uses the same endpoint
; as the dotnet-install scripts to determine the latest full version of a dotnet version
;
; \param[in] Version The desired dotnet core runtime version as a 2 digit version. e.g. 3.1, 6.0, 7.0
; \param[out] Result The full version number of the latest version - e.g. 6.0.7
!macro DotNetCoreGetLatestVersion Version Result
; Save registers
Push $R0
Push $R1
Push $R2
; Push and pop parameters so we don't have conflicts if parameters are $R#
Push ${Version}
Pop $R0 ; Version
StrCpy $R1 https://dotnetcli.azureedge.net/dotnet/WindowsDesktop/$R0/latest.version
DetailPrint "Querying latest version of dotnet $R0 from $R1"
; Fetch latest version of the desired dotnet version
; todo error handling in the PS script? so we can check for errors here
StrCpy $R2 "Write-Host (Invoke-WebRequest -UseBasicParsing -URI $\"$R1$\").Content;"
!insertmacro DotNetCorePSExec $R2 $R2
; $R2 contains latest version, e.g. 6.0.7
; todo error handling here
; Push the result onto the stack
${TrimNewLines} $R2 $R2
Push $R2
; Restore registers
Exch
Pop $R2
Exch
Pop $R1
Exch
Pop $R0
; Set result
Pop ${Result}
!macroend
!macro DotNetCoreGetInstalledVersion Version Result
!define DNC_INS_ID ${__LINE__}
; Save registers
Push $R0
Push $R1
Push $R2
; Push and pop parameters so we don't have conflicts if parameters are $R#
Push ${Version}
Pop $R0 ; Version
DetailPrint "Checking installed version of dotnet $R0"
StrCpy $R1 "dotnet --list-runtimes | % { if($$_ -match $\".*WindowsDesktop.*($R0.\d+).*$\") { $$matches[1] } } | Sort-Object {[int]($$_ -replace '\d.\d.(\d+)', '$$1')} -Descending | Select-Object -first 1"
!insertmacro DotNetCorePSExec $R1 $R1
; $R1 contains highest installed version, e.g. 6.0.7
${TrimNewLines} $R1 $R1
; If there is an installed version it should start with the same two "words" as the input version,
; otherwise assume we got an error response
; todo improve this simple test which checks there are at least 3 "words" separated by periods
${WordFind} $R1 "." "E#" $R2
IfErrors error_${DNC_INS_ID}
; $R2 contains number of version parts in R1 (dot separated words = version parts)
; If less than 3 parts, or more than 4 parts, error
IntCmp $R2 3 0 error_${DNC_INS_ID}
IntCmp $R2 4 0 0 error_${DNC_INS_ID}
; todo more error handling here / validation
; Seems to be OK, skip the "set to blank string" error handler
Goto end_${DNC_INS_ID}
error_${DNC_INS_ID}:
StrCpy $R1 "" ; Set result to blank string if any error occurs (means not installed)
end_${DNC_INS_ID}:
!undef DNC_INS_ID
; Push the result onto the stack
Push $R1
; Restore registers
Exch
Pop $R2
Exch
Pop $R1
Exch
Pop $R0
; Set result
Pop ${Result}
!macroend
!macro DotNetCoreInstallVersion Version
; Save registers
Push $R0
Push $R1
Push $R2
Push $R3
; Push and pop parameters so we don't have conflicts if parameters are $R#
Push ${Version}
Pop $R0 ; Version
${If} ${IsNativeAMD64}
StrCpy $R3 "x64"
${ElseIf} ${IsNativeARM64}
StrCpy $R3 "arm64"
${ElseIf} ${IsNativeIA32}
StrCpy $R3 "x86"
${Else}
StrCpy $R3 "unknown"
${EndIf}
; todo can download as a .zip, which is smaller, then we'd need to unzip it before running it...
StrCpy $R1 https://dotnetcli.azureedge.net/dotnet/WindowsDesktop/$R0/windowsdesktop-runtime-$R0-win-$R3.exe
; For dotnet versions less than 5 the WindowsDesktop runtime has a different path
${WordFind} $R0 "." "+1" $R2
IntCmp $R2 5 +2 0 +2
StrCpy $R1 https://dotnetcli.azureedge.net/dotnet/Runtime/$R0/windowsdesktop-runtime-$R0-win-$R3.exe
DetailPrint "Downloading dotnet $R0 from $R1"
; Create destination file
GetTempFileName $R2
nsExec::Exec 'cmd.exe /c rename "$R2" "$R2.exe"' ; Not using Rename to avoid spam in details log
Pop $R3 ; Pop exit code
StrCpy $R2 "$R2.exe"
; Fetch runtime installer
; todo error handling in the PS script? so we can check for errors here
StrCpy $R1 "Invoke-WebRequest -UseBasicParsing -URI $\"$R1$\" -OutFile $\"$R2$\""
!insertmacro DotNetCorePSExec $R1 $R1
; $R1 contains powershell script result
${WordFind} $R1 "BlobNotFound" "E+1{" $R3
ifErrors +3 0
DetailPrint "Dotnet installer $R0 not found."
Goto +10
; todo error handling for PS result, verify download result
IfFileExists $R2 +3, 0
DetailPrint "Dotnet installer did not download."
Goto +7
DetailPrint "Download complete"
DetailPrint "Installing dotnet $R0"
ExecWait "$\"$R2$\" /install /quiet /norestart" $R1
DetailPrint "Installer completed (Result: $R1)"
nsExec::Exec 'cmd.exe /c del "$R2"' ; Not using Delete to avoid spam in details log
Pop $R3 ; Pop exit code
; Error checking? Verify install result?
; Restore registers
Pop $R3
Pop $R2
Pop $R1
Pop $R0
!macroend
; below is adapted from https://nsis.sourceforge.io/PowerShell_support but avoids using the plugin
; directory in favour of a temp file and providing a return variable rather than returning on the
; stack. Methods renamed to avoid conflicting with use of the original macros
; DotNetCorePSExec
; Executes a powershell script
;
; \param[in] PSCommand The powershell command or script to execute
; \param[out] Result The output from the powershell script
!macro DotNetCorePSExec PSCommand Result
Push ${PSCommand}
Call DotNetCorePSExecFn
Pop ${Result}
!macroend
; DotNetCorePSExecFile
; Executes a powershell file
;
; \param[in] FilePath The path to the powershell script file to execute
; \param[out] Result The output from the powershell script
!macro DotNetCorePSExecFile FilePath Result
Push ${FilePath}
Call DotNetCorePSExecFileFn
Pop ${Result}
!macroend
Function DotNetCorePSExecFn
; Read parameters and save registers
Exch $R0 ; Script
Push $R1
Push $R2
; Write the command into a temp file
; Note: Using GetTempFileName to get a temp file name, but since we need to have a .ps1 extension
; on the end we rename it with an extra file extension
GetTempFileName $R1
nsExec::Exec 'cmd.exe /c rename "$R1" "$R1.ps1"' ; Not using Rename to avoid spam in details log
Pop $R2 ; Pop exit code
StrCpy $R1 "$R1.ps1"
FileOpen $R2 $R1 w
FileWrite $R2 $R0
FileClose $R2
; Execute the powershell script and delete the temp file
Push $R1
Call DotNetCorePSExecFileFn
nsExec::Exec 'cmd.exe /c del "$R1"' ; Not using Delete to avoid spam in details log
Pop $R0 ; Pop exit code
; Restore registers
Exch
Pop $R2
Exch
Pop $R1
Exch
Pop $R0
; Stack contains script output only, which we leave as the function result
FunctionEnd
Function DotNetCorePSExecFileFn
; Read parameters and save registers
Exch $R0 ; FilePath
Push $R1
nsExec::ExecToStack 'powershell -inputformat none -ExecutionPolicy RemoteSigned -File "$R0" '
; Stack contain exitCode, scriptOutput, registers
; Pop exit code & validate
Pop $R1
IntCmp $R1 0 +2
SetErrorLevel 2
; Restore registers
Exch
Pop $R1
Exch
Pop $R0
; Stack contains script output only, which we leave as the function result
FunctionEnd
!endif

View File

@@ -0,0 +1,12 @@
!incluide "dotnet.nsh"
!macro NSIS_HOOK_PREINSTALL
!insertmacro CheckDotNetCore 8.0
!macroend
!macro NSIS_HOOK_POSTINSTALL
${If} $PassiveMode = 1
${OrIf} ${Silent}
Exec '"$INSTDIR\${MAINBINARYNAME}.exe"'
${EndIf}
!macroend

View File

@@ -6,6 +6,7 @@ use std::path::PathBuf;
use sysinfo::System; use sysinfo::System;
use tauri::AppHandle; use tauri::AppHandle;
use tauri::Emitter; use tauri::Emitter;
use tauri::Manager;
use tokio::fs; use tokio::fs;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::process::Command; use tokio::process::Command;
@@ -720,7 +721,6 @@ pub async fn has_net8() -> bool {
is_net8_installed().await is_net8_installed().await
} }
#[cfg(windows)]
#[tauri::command] #[tauri::command]
pub fn encrypt_string(string: String, entropy: String) -> String { pub fn encrypt_string(string: String, entropy: String) -> String {
let encrypted = encrypt_password(&string, &entropy); let encrypted = encrypt_password(&string, &entropy);
@@ -731,9 +731,78 @@ pub fn encrypt_string(string: String, entropy: String) -> String {
} }
} }
// NOTE: should not be called by tauri on non windows systems, return the string nonthenless #[tauri::command]
pub async fn download_ezpp_launcher_update(app: AppHandle, url: String) -> Result<(), String> {
let client = Client::new();
let mut response = client.get(&url).send().await.map_err(|e| e.to_string())?;
if !response.status().is_success() {
return Err(format!("Failed to download update: {}", response.status()));
}
let temp_dir = app.path().temp_dir().expect("Failed to get temp directory");
let file_path = temp_dir.join("ezpplauncher_update.exe");
let mut file_out = fs::File::create(&file_path)
.await
.map_err(|e| e.to_string())?;
let mut downloaded = 0u64;
let size = response
.content_length()
.ok_or("Failed to get content length")? as usize;
while let Some(chunk) = response.chunk().await.map_err(|e| e.to_string())? {
downloaded += chunk.len() as u64;
file_out
.write_all(&chunk)
.await
.map_err(|e| e.to_string())?;
app.emit(
"download-progress",
UpdateStatus {
file_name: "Update".to_string(),
downloaded,
size: size,
progress: ((downloaded as f64 / size as f64 * 100.0) * 100.0).trunc() / 100.0,
},
)
.unwrap_or_default();
}
Ok(())
}
#[cfg(windows)]
#[tauri::command]
pub async fn install_ezpp_launcher_update(app: AppHandle) -> Result<(), String> {
let temp_dir = app.path().temp_dir().expect("Failed to get temp directory");
let file_path = temp_dir.join("ezpplauncher_update.exe");
if !file_path.exists() {
return Err("Update file does not exist".to_string());
}
// run this app detached and exit
const DETACHED_PROCESS: u32 = 0x00000008;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
Command::new(&file_path)
.arg("/S")
.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP)
.spawn()
.map_err(|e| format!("Failed to spawn updater: {}", e))?;
sleep(Duration::from_millis(250)).await;
app.exit(0x0100);
Ok(())
}
#[cfg(not(windows))] #[cfg(not(windows))]
#[tauri::command] #[tauri::command]
pub fn encrypt_string(string: String, _entropy: String) -> String { pub async fn install_ezpp_launcher_update(_app: AppHandle) -> Result<(), String> {
string Ok(())
} }

View File

@@ -5,10 +5,11 @@ pub mod commands;
pub mod presence; pub mod presence;
pub mod utils; pub mod utils;
use crate::commands::{ use crate::commands::{
check_for_corruption, download_ezpp_launcher_update_files, encrypt_string, exit, check_for_corruption, download_ezpp_launcher_update, download_ezpp_launcher_update_files,
find_osu_installation, get_beatmapsets_count, get_ezpp_launcher_update_files, get_hwid, encrypt_string, exit, find_osu_installation, get_beatmapsets_count,
get_launcher_version, get_osu_release_stream, get_osu_skin, get_osu_version, get_platform, get_ezpp_launcher_update_files, get_hwid, get_launcher_version, get_osu_release_stream,
get_skins_count, has_net8, has_osuwinello, has_wmctrl, is_osu_running, open_url_in_browser, get_osu_skin, get_osu_version, get_platform, get_skins_count, has_net8, has_osuwinello,
has_wmctrl, install_ezpp_launcher_update, is_osu_running, open_url_in_browser,
presence_connect, presence_disconnect, presence_is_connected, presence_update_status, presence_connect, presence_disconnect, presence_is_connected, presence_update_status,
presence_update_user, replace_ui_files, run_osu, run_osu_updater, set_osu_config_values, presence_update_user, replace_ui_files, run_osu, run_osu_updater, set_osu_config_values,
set_osu_user_config_values, valid_osu_folder, set_osu_user_config_values, valid_osu_folder,
@@ -61,13 +62,14 @@ pub fn run() {
has_osuwinello, has_osuwinello,
has_wmctrl, has_wmctrl,
has_net8, has_net8,
encrypt_string encrypt_string,
download_ezpp_launcher_update,
install_ezpp_launcher_update
]) ])
.plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_cors_fetch::init()) .plugin(tauri_plugin_cors_fetch::init())
.plugin(tauri_plugin_sql::Builder::default().build())
.build(tauri::generate_context!()) .build(tauri::generate_context!())
.expect("error while building tauri application"); .expect("error while building tauri application");

View File

@@ -144,7 +144,7 @@ impl PresenceActor {
"Download the Launcher", "Download the Launcher",
"https://git.ez-pp.farm/EZPPFarm/EZPPLauncher/releases/latest", "https://git.ez-pp.farm/EZPPFarm/EZPPLauncher/releases/latest",
), ),
Button::new("Join EZPZFarm", "https://ez-pp.farm/discord"), Button::new("Join EZPPFarm", "https://ez-pp.farm/discord"),
]); ]);
if let Err(e) = client.set_activity(activity).map_err(|e| e.to_string()) { if let Err(e) = client.set_activity(activity).map_err(|e| e.to_string()) {

View File

@@ -286,8 +286,18 @@ pub fn get_window_title_by_pid(pid: Pid) -> String {
} }
pub async fn is_net8_installed() -> bool { pub async fn is_net8_installed() -> bool {
use std::process::Command; use tokio::process::Command;
let output_result = Command::new("dotnet").arg("--list-runtimes").output();
let mut command = Command::new("dotnet");
command.arg("--list-runtimes");
#[cfg(windows)]
{
const CREATE_NO_WINDOW: u32 = 0x08000000;
command.creation_flags(CREATE_NO_WINDOW);
}
let output_result = command.output().await;
match output_result { match output_result {
Ok(output) => { Ok(output) => {
if !output.status.success() { if !output.status.success() {
@@ -300,14 +310,17 @@ pub async fn is_net8_installed() -> bool {
} }
let stdout_str = String::from_utf8_lossy(&output.stdout); let stdout_str = String::from_utf8_lossy(&output.stdout);
stdout_str stdout_str.lines().any(|line| line.starts_with("Microsoft.WindowsDesktop.App 8."))
.lines()
.any(|line| line.starts_with("Microsoft.WindowsDesktop.App 8."))
} }
Err(_) => false, Err(_) => false,
} }
} }
#[cfg(not(windows))]
pub fn encrypt_password(password: &str, _entropy: &str) -> Result<String, String> {
Ok(password.to_string())
}
#[cfg(windows)] #[cfg(windows)]
pub fn encrypt_password(password: &str, entropy: &str) -> Result<String, String> { pub fn encrypt_password(password: &str, entropy: &str) -> Result<String, String> {
use base64::{Engine as _, engine::general_purpose}; use base64::{Engine as _, engine::general_purpose};

View File

@@ -1,8 +1,9 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "ezpplauncher", "productName": "EZPPLauncher",
"version": "3.0.1", "version": "3.0.4",
"identifier": "farm.ezpp.launcher", "identifier": "farm.EZPPFarm.Launcher",
"mainBinaryName": "ezpplauncher",
"build": { "build": {
"beforeDevCommand": "bun run vite:dev", "beforeDevCommand": "bun run vite:dev",
"devUrl": "http://localhost:1420", "devUrl": "http://localhost:1420",
@@ -12,7 +13,7 @@
"app": { "app": {
"windows": [ "windows": [
{ {
"title": "ezpplauncher", "title": "EZPPLauncher",
"width": 1000, "width": 1000,
"height": 700, "height": 700,
"decorations": false, "decorations": false,
@@ -26,10 +27,26 @@
} }
}, },
"bundle": { "bundle": {
"publisher": "EZPPFarm",
"copyright": "EZPPFarm",
"homepage": "https://ez-pp.farm",
"license": "GPL-3.0",
"active": true, "active": true,
"targets": [ "targets": [
"app" "nsis",
"appimage"
], ],
"windows": {
"webviewInstallMode": {
"silent": true,
"type": "downloadBootstrapper"
},
"nsis": {
"installMode": "currentUser",
"compression": "lzma",
"installerHooks": "./nsis/nsis-hooks.nsh"
}
},
"icon": [ "icon": [
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",

View File

@@ -2,7 +2,7 @@
@import "tw-animate-css"; @import "tw-animate-css";
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:where(.dark, .dark *));
:root { :root {
--radius: 0.625rem; --radius: 0.625rem;

View File

@@ -18,11 +18,13 @@ export const osuapi = {
}, },
}); });
if (request.error) { if (request.error) {
if (request.error.status >= 500 && request.error.status < 600)
throw new Error('Server not reachable');
return undefined; return undefined;
} }
const releaseData = request.data; const releaseData = request.data;
if (!releaseData || !releaseData.streams) {
return undefined;
}
if (releaseData.streams.length === 0) return undefined;
const selectedRelease = releaseData.streams.find( const selectedRelease = releaseData.streams.find(
(releaseBuild) => (releaseBuild) =>
releaseBuild.name.toLowerCase() === releaseStream.replaceAll(' ', '').toLowerCase() releaseBuild.name.toLowerCase() === releaseStream.replaceAll(' ', '').toLowerCase()

View File

@@ -1,150 +0,0 @@
<script lang="ts">
import ezppLogo from '../../../../assets/logo.png';
import { osudirect } from '@/api/osudirect';
import { gameSounds } from '@/utils';
import { BeatmapDecoder } from 'osu-parsers';
import { onMount } from 'svelte';
type logoProps = {
beatmapId: number;
extended: boolean;
onclick: () => void;
};
let { beatmapId, extended, onclick }: logoProps = $props();
let hovered = $state(false);
let bpm = $state(150); // 1000 * 60 / bpm
let lastTimeout: number | undefined = undefined;
onMount(async () => {
gameSounds.preload();
const beatmapData = await osudirect.osu(beatmapId);
if (beatmapData) {
const decoder = new BeatmapDecoder();
const beatmap = decoder.decodeFromString(beatmapData);
console.log(beatmap);
const audio = new Audio(`https://osu.direct/api/media/audio/${beatmapId}`);
audio.volume = 0.3;
// Function to play the heartbeat sound
const playHeartbeat = () => {
gameSounds.play('menuHeartbeat', {
volume: hovered ? 1 : 0.3,
});
};
// Function to synchronize the heartbeat with the song
const syncHeartbeat = () => {
const currentTime = audio.currentTime * 1000 - 25; // Convert to milliseconds
const timingPoint = beatmap.controlPoints.timingPointAt(currentTime);
const timingPointTime = timingPoint.startTime;
console.log(currentTime, timingPointTime);
if (timingPoint && bpm !== timingPoint.bpm) {
bpm = timingPoint.bpm;
if (lastTimeout) window.clearTimeout(lastTimeout);
const interval = (1000 * 60) / bpm; // Interval in milliseconds
const nextBeat = currentTime % interval; // Invert: time since last beat
// Schedule the next heartbeat at the correct time
lastTimeout = window.setTimeout(() => {
playHeartbeat();
// Clear any previous interval
if (lastTimeout) {
window.clearInterval(lastTimeout);
lastTimeout = undefined;
}
if (!lastTimeout) lastTimeout = window.setInterval(playHeartbeat, interval);
}, nextBeat);
}
// Continue syncing
requestAnimationFrame(syncHeartbeat);
};
// Wait for audio to be ready before starting playback and syncing
audio.addEventListener('canplay', async () => {
audio.addEventListener('play', syncHeartbeat);
audio.addEventListener('ended', async () => await audio.play());
await audio.play();
syncHeartbeat();
});
}
});
</script>
<div
class="w-screen {extended
? hovered
? '-translate-y-1 scale-100'
: '-translate-y-1 scale-90'
: hovered
? 'translate-y-5 scale-150'
: 'translate-y-5 scale-125'} transition-transform select-none"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="logo-animation relative w-44 h-44 mx-auto cursor-pointer"
onmouseenter={() => (hovered = true)}
onmouseleave={() => (hovered = false)}
onclick={() => {
if (extended) {
gameSounds.play('menuBack');
} else {
gameSounds.play('menuHit');
}
onclick();
}}
>
<img
class="absolute pulse-logo"
style="animation-duration: {(1000 * 60) / bpm}ms;"
src={ezppLogo}
alt="logo-pulse"
/>
<img
class="absolute main-logo"
style="animation-duration: {(1000 * 60) / bpm}ms;"
src={ezppLogo}
alt="logo"
/>
</div>
</div>
<style lang="scss">
.logo-animation {
.pulse-logo {
animation: 0.5s 0.2s infinite forwards beat-pulse;
}
.main-logo {
animation: 0.5s infinite forwards beat;
}
}
@keyframes beat {
0%,
100% {
scale: 1;
}
90% {
scale: 1.08;
}
}
@keyframes beat-pulse {
0%,
10% {
scale: 0.5;
opacity: 0.5;
filter: blur(0px);
}
100% {
scale: 1.4;
opacity: 0;
filter: blur(2px);
}
}
</style>

View File

@@ -138,3 +138,17 @@ export const hasOsuWinello = async () => await invoke<boolean>('has_osuwinello')
export const hasNet8 = async () => await invoke<boolean>('has_net8'); export const hasNet8 = async () => await invoke<boolean>('has_net8');
export const encryptString = async (str: string, entropy: string) => export const encryptString = async (str: string, entropy: string) =>
await invoke<string>('encrypt_string', { string: str, entropy }); await invoke<string>('encrypt_string', { string: str, entropy });
export const downloadUpdate = async (
url: string,
progressCallback: (file: UpdateStatus) => void
) => {
const downloadStatusListen = await listen('download-progress', (event) =>
progressCallback(event.payload as UpdateStatus)
);
try {
await invoke<string>('download_ezpp_launcher_update', { url });
} finally {
downloadStatusListen();
}
};
export const installUpdate = async () => await invoke('install_ezpp_launcher_update');

View File

@@ -53,7 +53,7 @@
releaseStreamToReadable, releaseStreamToReadable,
urlIsValidImage, urlIsValidImage,
} from '@/utils'; } from '@/utils';
import { fade, scale } from 'svelte/transition'; import { crossfade, fade, scale } from 'svelte/transition';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import Label from '@/components/ui/label/label.svelte'; import Label from '@/components/ui/label/label.svelte';
import { import {
@@ -83,6 +83,7 @@
import { osuapi } from '@/api/osuapi'; import { osuapi } from '@/api/osuapi';
import { import {
downloadEZPPLauncherUpdateFiles, downloadEZPPLauncherUpdateFiles,
downloadUpdate,
encryptString, encryptString,
exit, exit,
getBeatmapSetsCount, getBeatmapSetsCount,
@@ -94,6 +95,7 @@
hasNet8, hasNet8,
hasOsuWinello, hasOsuWinello,
hasWMCTRL, hasWMCTRL,
installUpdate,
isOsuCorrupted, isOsuCorrupted,
isOsuRunning, isOsuRunning,
isValidOsuFolder, isValidOsuFolder,
@@ -108,12 +110,26 @@
import Hearts from '@/components/ui/effects/Hearts.svelte'; import Hearts from '@/components/ui/effects/Hearts.svelte';
import { EZPPActionStatus } from '@/types'; import { EZPPActionStatus } from '@/types';
import * as presence from '@/presence'; import * as presence from '@/presence';
import { expoOut } from 'svelte/easing';
const tabs = [
{ name: 'Home', key: 'home' },
{
name: 'Settings',
key: 'settings',
},
];
let selectedTab = $state('home'); let selectedTab = $state('home');
let [tab_send, tab_receive] = crossfade({
duration: $reduceAnimations ? 0 : 400,
easing: expoOut,
});
let progress = $state(-1); let progress = $state(-1);
let launchInfo = $state(''); let launchInfo = $state('');
let launchError = $state<Error | undefined>(undefined); let launchError = $state<Error | undefined>(undefined);
let downloadingUpdate = $state(false);
let selectedGamemode = $derived( let selectedGamemode = $derived(
getGamemodeInt(modeIntToStr($preferredMode), typeIntToStr($preferredType)) getGamemodeInt(modeIntToStr($preferredMode), typeIntToStr($preferredType))
); );
@@ -149,14 +165,11 @@
toast.success('osu! installation path set successfully.'); toast.success('osu! installation path set successfully.');
const beatmapSetCount: number | null = await getBeatmapSetsCount(selectedPath); const beatmapSetCount: number | null = await getBeatmapSetsCount(selectedPath);
if (beatmapSetCount) { if (beatmapSetCount) beatmapSets.set(beatmapSetCount);
beatmapSets.set(beatmapSetCount);
}
const skinsCount: number | null = await getSkinsCount(selectedPath); const skinsCount: number | null = await getSkinsCount(selectedPath);
if (skinsCount) { if (skinsCount !== null) skins.set(skinsCount);
skins.set(skinsCount);
}
const skin: string = await getSkin(selectedPath); const skin: string = await getSkin(selectedPath);
currentSkin.set(skin); currentSkin.set(skin);
} }
@@ -244,13 +257,31 @@
const streamInfo = await osuapi.latestBuildVersion('stable40'); const streamInfo = await osuapi.latestBuildVersion('stable40');
if (!streamInfo) { if (!streamInfo) {
toast.error('Hmmm...', { toast.error('Hmmm...', {
description: 'Failed to check for updates.', description: 'Failed to check for updates, maybe osu! is down?',
}); });
launching.set(false); launching.set(false);
return; return;
} }
const releaseStream = await getReleaseStream(osuPath); const releaseStream = await getReleaseStream(osuPath);
if (releaseStream === undefined) {
toast.error('Hmmm...', {
description: 'Failed to get osu! release stream.',
});
launching.set(false);
return;
}
// only stable osu! release streams are supported for now
if (!releaseStream.toLowerCase().includes('stable')) {
toast.error('Hmmm...', {
description: 'You are not on the stable release stream, please switch to it.',
});
launching.set(false);
return;
}
const osuCorrupted = await isOsuCorrupted(osuPath); const osuCorrupted = await isOsuCorrupted(osuPath);
let forceUpdate = let forceUpdate =
(releaseStream && releaseStream.toLowerCase() !== 'stable40') || osuCorrupted; (releaseStream && releaseStream.toLowerCase() !== 'stable40') || osuCorrupted;
@@ -481,7 +512,7 @@
if (beatmapSetCount) beatmapSets.set(beatmapSetCount); if (beatmapSetCount) beatmapSets.set(beatmapSetCount);
const skinCount = await getSkinsCount(osuPath); const skinCount = await getSkinsCount(osuPath);
if (skinCount) skins.set(skinCount); if (skinCount !== null) skins.set(skinCount);
const skin = await getSkin(osuPath); const skin = await getSkin(osuPath);
currentSkin.set(skin); currentSkin.set(skin);
@@ -504,7 +535,7 @@
</script> </script>
<AlertDialog.Root open={launchError !== undefined}> <AlertDialog.Root open={launchError !== undefined}>
<AlertDialog.Content class="bg-theme-950 border-theme-800 p-0"> <AlertDialog.Content class="bg-theme-950 border-theme-800 p-0 max-w-[90vw]">
<div <div
class="flex flex-col items-center justify-center border-b border-theme-800 bg-black/40 rounded-t-lg p-3" class="flex flex-col items-center justify-center border-b border-theme-800 bg-black/40 rounded-t-lg p-3"
> >
@@ -512,10 +543,10 @@
<span class="font-semibold text-xl">Error on Launch!</span> <span class="font-semibold text-xl">Error on Launch!</span>
</div> </div>
<div <div
class="flex flex-col items-center text-sm text-center bg-theme-900 border border-theme-800 rounded-lg mx-3 p-3" class="flex flex-col items-center text-sm text-center bg-theme-900 border border-theme-800 rounded-lg mx-3 p-3 overflow-hidden"
> >
{#if launchError} {#if launchError}
<pre class="text-wrap text-start">{JSON.stringify( <pre class="text-wrap text-start overflow-auto w-full">{JSON.stringify(
launchError, launchError,
Object.getOwnPropertyNames(launchError), Object.getOwnPropertyNames(launchError),
2 2
@@ -562,14 +593,45 @@
</div> </div>
</div> </div>
<div class="flex items-center justify-center mb-3"> <div class="flex items-center justify-center mb-3">
<Button {#if $platform === 'windows'}
onclick={async () => { {#if downloadingUpdate}
if ($newVersion) { <div class="flex flex-col items-center justify-center gap-2 p-3 rounded-lg w-full">
await openURL($newVersion.html_url); <Progress indeterminate={progress === -1} value={progress} />
await exit(); <span class="text-muted-foreground text-sm mt-4">{launchInfo}</span>
} </div>
}}>Update now</Button {:else}
> <Button
onclick={async () => {
const updateFile = $newVersion?.assets.find((asset) => asset.name.endsWith('.exe'));
if (!updateFile) {
toast.error('Hmmm...', {
description: 'No update file found.',
});
$newVersion = undefined;
return;
}
downloadingUpdate = true;
launchInfo = 'Downloading Update...';
await downloadUpdate(updateFile.browser_download_url, (file) => {
progress = file.progress;
launchInfo = `Downloading Update (${formatBytes(file.downloaded)}/${formatBytes(file.size)})...`;
});
progress = -1;
launchInfo = 'Update downloaded, installing...';
await installUpdate();
}}>Install Update now</Button
>
{/if}
{:else}
<Button
onclick={async () => {
if ($newVersion) {
await openURL($newVersion.html_url);
await exit();
}
}}>Update now</Button
>
{/if}
</div> </div>
</AlertDialog.Content> </AlertDialog.Content>
</AlertDialog.Root> </AlertDialog.Root>
@@ -839,24 +901,25 @@
<div <div
class="flex flex-row flex-nowrap h-11 gap-1 w-full bg-theme-800/50 border border-theme-800/90 rounded-lg p-[4px]" class="flex flex-row flex-nowrap h-11 gap-1 w-full bg-theme-800/50 border border-theme-800/90 rounded-lg p-[4px]"
> >
<button {#each tabs as tab (tab.key)}
class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab === <button
'home' class="inline-block relative py-2 px-2 disabled:opacity-25 transition-opacity w-full text-center"
? 'bg-white/70 border border-white/60 text-theme-950' onclick={() => (selectedTab = tab.key)}
: ''} transition-all" >
onclick={() => (selectedTab = 'home')} <div
> class="flex flex-row items-center justify-center gap-1 text-xs md:text-sm font-semibold w-full text-center"
Home >
</button> <div>{tab.name}</div>
<button </div>
class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab === {#if selectedTab === tab.key}
'settings' <div
? 'bg-white/70 border border-white/60 text-theme-950' in:tab_receive={{ key: 'tab' }}
: ''} transition-all" out:tab_send={{ key: 'tab' }}
onclick={() => (selectedTab = 'settings')} class="absolute left-0 bottom-0 w-full h-full rounded-lg bg-white/10 border border-white/10"
> ></div>
Settings {/if}
</button> </button>
{/each}
</div> </div>
{#if selectedTab === 'home'} {#if selectedTab === 'home'}
<div <div
@@ -900,13 +963,17 @@
</div> </div>
<div class="relative font-bold text-xl text-yellow-400"> <div class="relative font-bold text-xl text-yellow-400">
<div <div
class="absolute top-1 left-1/2 -translate-x-1/2 {!$skins class="absolute top-1 left-1/2 -translate-x-1/2 {!$skins && $skins !== 0
? 'opacity-100' ? 'opacity-100'
: 'opacity-0'} transition-opacity duration-1000" : 'opacity-0'} transition-opacity duration-1000"
> >
<LoaderCircle class="animate-spin" /> <LoaderCircle class="animate-spin" />
</div> </div>
<div class="{!$skins ? 'opacity-0' : 'opacity-100'} transition-opacity duration-1000"> <div
class="{!$skins && $skins !== 0
? 'opacity-0'
: 'opacity-100'} transition-opacity duration-1000"
>
{#if $reduceAnimations} {#if $reduceAnimations}
<span>{numberHumanReadable($skins ?? 0)}</span> <span>{numberHumanReadable($skins ?? 0)}</span>
{:else} {:else}

View File

@@ -135,11 +135,11 @@
currentLoadingInfo.set('Counting beatmapsets...'); currentLoadingInfo.set('Counting beatmapsets...');
const beatmapSetCount = await getBeatmapSetsCount($osuInstallationPath); const beatmapSetCount = await getBeatmapSetsCount($osuInstallationPath);
if (beatmapSetCount) beatmapSets.set(beatmapSetCount); if (beatmapSetCount !== null) beatmapSets.set(beatmapSetCount);
currentLoadingInfo.set('Counting skins...'); currentLoadingInfo.set('Counting skins...');
const skinCount = await getSkinsCount($osuInstallationPath); const skinCount = await getSkinsCount($osuInstallationPath);
if (skinCount) skins.set(skinCount); if (skinCount !== null) skins.set(skinCount);
const skin: string = await getSkin($osuInstallationPath); const skin: string = await getSkin($osuInstallationPath);
currentSkin.set(skin); currentSkin.set(skin);
} }