diff --git a/bun.lock b/bun.lock index 24cad72..8a4b1f0 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,9 @@ "@tauri-apps/plugin-shell": "2.3.0", "@tauri-apps/plugin-sql": "2.3.0", "animejs": "^4.0.2", + "buffer": "^6.0.3", + "crypto": "^1.0.1", + "crypto-js": "^4.2.0", "ky": "1.8.1", "lucide-svelte": "0.523.0", "osu-classes": "3.1.0", @@ -25,6 +28,7 @@ "@sveltejs/kit": "2.22.2", "@sveltejs/vite-plugin-svelte": "5.1.0", "@tauri-apps/cli": "2.6.1", + "@types/crypto-js": "^4.2.2", "autoprefixer": "10.4.21", "bits-ui": "^1.4.7", "clsx": "2.1.1", @@ -227,6 +231,8 @@ "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], + "@types/crypto-js": ["@types/crypto-js@4.2.2", "", {}, "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/node": ["@types/node@20.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA=="], @@ -253,6 +259,8 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], "bits-ui": ["bits-ui@1.8.0", "", { "dependencies": { "@floating-ui/core": "^1.6.4", "@floating-ui/dom": "^1.6.7", "@internationalized/date": "^3.5.6", "css.escape": "^1.5.1", "esm-env": "^1.1.2", "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1", "tabbable": "^6.2.0" }, "peerDependencies": { "svelte": "^5.11.0" } }, "sha512-CXD6Orp7l8QevNDcRPLXc/b8iMVgxDWT2LyTwsdLzJKh9CxesOmPuNePSPqAxKoT59FIdU4aFPS1k7eBdbaCxg=="], @@ -263,6 +271,8 @@ "browserslist": ["browserslist@4.25.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw=="], + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "buffer-builder": ["buffer-builder@0.2.0", "", {}, "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg=="], "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], @@ -285,6 +295,10 @@ "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": ["crypto@1.0.1", "", {}, "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig=="], + + "crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="], + "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], @@ -337,6 +351,8 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "immutable": ["immutable@5.1.3", "", {}, "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg=="], "inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="], diff --git a/package.json b/package.json index c17589e..a7b172e 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,9 @@ "@tauri-apps/plugin-shell": "2.3.0", "@tauri-apps/plugin-sql": "2.3.0", "animejs": "^4.0.2", + "buffer": "^6.0.3", + "crypto": "^1.0.1", + "crypto-js": "^4.2.0", "ky": "1.8.1", "lucide-svelte": "0.523.0", "osu-classes": "3.1.0", @@ -37,6 +40,7 @@ "@sveltejs/kit": "2.22.2", "@sveltejs/vite-plugin-svelte": "5.1.0", "@tauri-apps/cli": "2.6.1", + "@types/crypto-js": "^4.2.2", "autoprefixer": "10.4.21", "bits-ui": "^1.4.7", "clsx": "2.1.1", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 0171554..f733987 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -836,7 +836,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -966,7 +966,7 @@ dependencies = [ "rustc_version", "toml", "vswhom", - "winreg", + "winreg 0.55.0", ] [[package]] @@ -1073,6 +1073,7 @@ dependencies = [ name = "ezpplauncher" version = "0.1.0" dependencies = [ + "hardware-id", "serde", "serde_json", "serde_repr", @@ -1586,6 +1587,16 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "hardware-id" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3165b5280ce11f886e353961b966becc45a598a9440707dec3e1cdd8f07cbe7" +dependencies = [ + "thiserror 1.0.69", + "winreg 0.10.1", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -5809,6 +5820,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "winreg" version = "0.55.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4b807a8..d124217 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,6 +26,7 @@ serde_repr = "0.1.20" tauri-plugin-sql = "2.3.0" tauri-plugin-dialog = "2.3.0" tauri-plugin-fs = "2.4.0" +hardware-id = "0.3.0" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-single-instance = "2.3.0" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 59973b2..701edc2 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -14,6 +14,62 @@ "core:window:allow-start-dragging", "core:window:allow-minimize", "core:window:allow-close", - "fs:default" + "fs:default", + { + "identifier": "fs:allow-write", + "allow": [ + { + "path": "$HOME/**/*" + } + ] + }, + { + "identifier": "fs:allow-read", + "allow": [ + { + "path": "$HOME/**/*" + } + ] + }, + { + "identifier": "fs:allow-exists", + "allow": [ + { + "path": "$HOME/**/*" + } + ] + }, + { + "identifier": "fs:allow-write-file", + "allow": [ + { + "path": "$HOME/**/*" + } + ] + }, + { + "identifier": "fs:allow-read-file", + "allow": [ + { + "path": "$HOME/**/*" + } + ] + }, + { + "identifier": "fs:allow-read-text-file", + "allow": [ + { + "path": "$HOME/**/*" + } + ] + }, + { + "identifier": "fs:allow-mkdir", + "allow": [ + { + "path": "$HOME/**/*" + } + ] + } ] } \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 87b6c10..4131b28 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,6 +1,13 @@ // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ +use hardware_id::get_id; use tauri::Manager; +#[tauri::command] +fn get_hwid() -> String { + let hwid = get_id().unwrap(); + hwid.into() +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let mut builder = tauri::Builder::default().plugin(tauri_plugin_fs::init()); @@ -16,9 +23,10 @@ pub fn run() { } builder + .invoke_handler(tauri::generate_handler![get_hwid]) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_sql::Builder::default().build()) .run(tauri::generate_context!()) .expect("error while running tauri application"); -} \ No newline at end of file +} diff --git a/src/lib/components/ui/osu-cursor/OsuCursor.svelte b/src/lib/components/ui/osu-cursor/OsuCursor.svelte index 894523d..4716ae6 100644 --- a/src/lib/components/ui/osu-cursor/OsuCursor.svelte +++ b/src/lib/components/ui/osu-cursor/OsuCursor.svelte @@ -4,6 +4,9 @@ import { animate } from 'animejs'; import { onMount } from 'svelte'; + let { smoothCursor = true }: { smoothCursor?: boolean } = + $props(); + let mouseX = $state(0); let mouseY = $state(0); let rotation = $state(0); @@ -75,7 +78,7 @@ } animate(cursor, { - duration: 180, + duration: smoothCursor ? 180 : 0, translateX: mouseX, translateY: mouseY - 50, ease: (t: number) => (t - 1) ** 3 + 1, @@ -128,19 +131,12 @@ let cursorInner: HTMLDivElement; let cursorAdditive: HTMLImageElement; - /* onMount(() => { - const processMouseMove = (e: MouseEvent) => - handleMouseMove(e.clientX, e.clientY, e.pageX, e.pageY); - document.addEventListener('pointermove', processMouseMove); - document.addEventListener('pointerdown', handleMouseDown); - document.addEventListener('pointerup', handleMouseUp); - + onMount(() => { + document.documentElement.classList.add('hiddenCursor'); return () => { - document.removeEventListener('pointermove', processMouseMove); - document.removeEventListener('pointerdown', handleMouseDown); - document.removeEventListener('pointerup', handleMouseUp); + document.documentElement.classList.remove('hiddenCursor'); }; - }); */ + }); - -
+
cursor diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..2f012bf --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,77 @@ +import { + BaseDirectory, + exists, + mkdir, + readFile, + readTextFile, + writeFile, +} from '@tauri-apps/plugin-fs'; +import * as path from '@tauri-apps/api/path'; +import { invoke } from '@tauri-apps/api/core'; +import { Crypto } from './crypto'; +import { enc } from 'crypto-js'; + +export class Config { + private config: Record = {}; + private crypto: Crypto | undefined; + private configFilePath: string | undefined; + + async init() { + const hwid: string = (await invoke('get_hwid')) ?? 'recorderinsandybridge'; + + this.crypto = new Crypto(hwid); + + const homeDir = await path.homeDir(); + const folderPath = await path.join(homeDir, '.ezpplauncher'); + this.configFilePath = await path.join(folderPath, 'user_settings'); + + const createFolder = !(await exists(folderPath)); + if (createFolder) await mkdir(folderPath); + + const createConfig = !(await exists(this.configFilePath)); + if (createConfig) await this.save(); + else await this.load(); + } + + private async load() { + if (!this.configFilePath) throw Error('configFilePath not set'); + if (!this.crypto) throw Error('crypto not initialized'); + + const fileStream = await readTextFile(this.configFilePath); + try { + const decryptedJSON = JSON.parse(this.crypto.decrypt(fileStream)) as Record; + this.config = decryptedJSON; + console.log('config file loaded'); + console.log(JSON.stringify(this.config)); + } catch (err) { + console.log('failed to read file'); + this.config = {}; + await this.save(); + } + } + + async save() { + if (!this.configFilePath) throw Error('configFilePath not set'); + if (!this.crypto) throw Error('crypto not initialized'); + const encryptedJSON = this.crypto.encrypt(JSON.stringify(this.config)); + + console.log(this.config); + console.log('saving file...'); + console.log(encryptedJSON); + await writeFile(this.configFilePath, Buffer.from(encryptedJSON), { + append: false, + }); + } + + value(key: string) { + console.log(this.config); + return { + set: (val: T) => { + this.config[key] = val; + }, + get: (fallback: T): T => { + return (this.config[key] as T) ?? fallback; + }, + }; + } +} diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts new file mode 100644 index 0000000..076a846 --- /dev/null +++ b/src/lib/crypto.ts @@ -0,0 +1,37 @@ +import cryptojs from 'crypto-js'; + +export class Crypto { + private key: cryptojs.lib.WordArray; + private ivLength: number; + + constructor(key: string, opts?: { ivLength?: number }) { + this.key = cryptojs.SHA256(key); + this.ivLength = opts?.ivLength ?? 16; + } + + encrypt(str: string): string { + const iv = cryptojs.lib.WordArray.random(this.ivLength); + const encrypted = cryptojs.AES.encrypt(str, this.key, { iv }); + + const ivBase64 = iv.toString(cryptojs.enc.Base64); + const ctBase64 = encrypted.ciphertext.toString(cryptojs.enc.Base64); + + return `${ivBase64}:${ctBase64}`; + } + + decrypt(data: string): string { + const [ivBase64, ctBase64] = data.split(':'); + + if (!ivBase64 || !ctBase64) throw new Error('Invalid input format'); + + const iv = cryptojs.enc.Base64.parse(ivBase64); + const ciphertext = cryptojs.enc.Base64.parse(ctBase64); + + const cipherParams = cryptojs.lib.CipherParams.create({ + ciphertext, + }); + + const decrypted = cryptojs.AES.decrypt(cipherParams, this.key, { iv }); + return decrypted.toString(cryptojs.enc.Utf8); + } +} diff --git a/src/lib/userSettings.ts b/src/lib/userSettings.ts new file mode 100644 index 0000000..ed9dd88 --- /dev/null +++ b/src/lib/userSettings.ts @@ -0,0 +1,7 @@ +import { writable } from 'svelte/store'; +import { Config } from './config'; + +export const userSettings = writable(new Config()); + +export const customCursor = writable(true); +export const cursorSmoothening = writable(true); diff --git a/src/pages/Launch.svelte b/src/pages/Launch.svelte index 0a1c7cb..f27ea48 100644 --- a/src/pages/Launch.svelte +++ b/src/pages/Launch.svelte @@ -6,7 +6,17 @@ import * as Select from '@/components/ui/select'; import { beatmap_sets, online_friends, server_connection_fails, server_ping } from '@/global'; import { WebviewWindow } from '@tauri-apps/api/webviewWindow'; - import { LoaderCircle, Logs, Music2, Play, Users, Wifi, Gamepad2, WifiOff, Settings2 } from 'lucide-svelte'; + import { + LoaderCircle, + Logs, + Music2, + Play, + Users, + Wifi, + Gamepad2, + WifiOff, + Settings2, + } from 'lucide-svelte'; import { Circle } from 'radix-icons-svelte'; import NumberFlow from '@number-flow/svelte'; import * as AlertDialog from '@/components/ui/alert-dialog'; @@ -15,6 +25,7 @@ import { fade, fly, scale } from 'svelte/transition'; import { Checkbox } from '@/components/ui/checkbox'; import Label from '@/components/ui/label/label.svelte'; + import { cursorSmoothening, customCursor, userSettings } from '@/userSettings'; let selectedTab = $state('settings'); let launching = $state(false); @@ -273,24 +284,48 @@ class="bg-theme-900/90 flex flex-col justify-center gap-3 border border-theme-800/90 rounded-lg" in:scale={{ duration: 400, start: 0.98 }} > -
EZPPLauncher Settings
-
+
+ EZPPLauncher Settings +
+
Enable a custom cursor in the Launcher like in the lazer build of osu!
- { + if (!e) { + cursorSmoothening.set(false); + } + customCursor.set(e); + + $userSettings.save(); + }} + class="flex items-center justify-center w-5 h-5" >
- +
Makes the custom cursor movement smoother.
- { + if (!$customCursor) return; + cursorSmoothening.set(e); + $userSettings.save(); + }} + disabled={!$customCursor} + class="flex items-center justify-center w-5 h-5" >
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 3bc4713..6cd7169 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -4,12 +4,31 @@ import { setupValues } from '@/global'; import { onMount } from 'svelte'; import OsuCursor from '@/components/ui/osu-cursor/OsuCursor.svelte'; + import { cursorSmoothening, customCursor, userSettings } from '@/userSettings'; + import { Buffer } from 'buffer'; let { children } = $props(); - onMount(setupValues); + onMount(async () => { + window.Buffer = Buffer; + setupValues(); + await $userSettings.init(); + + const config_custom_cursor = $userSettings.value('custom_cursor'); + const config_cursor_smoothening = $userSettings.value('cursor_smoothening'); + + console.log("yes", config_cursor_smoothening.get(true)) + + customCursor.set(config_custom_cursor.get(true)); + cursorSmoothening.set(config_cursor_smoothening.get(true)); + + customCursor.subscribe((val) => config_custom_cursor.set(val)); + cursorSmoothening.subscribe((val) => config_cursor_smoothening.set(val)); + }); - +{#if $customCursor} + +{/if}
diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 958328b..65aa149 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -1,6 +1,6 @@ // Tauri doesn't have a Node.js server to do proper SSR // so we will use adapter-static to prerender the app (SSG) + // See: https://v2.tauri.app/start/frontend/sveltekit/ for more info export const prerender = true; -export const ssr = false; - +export const ssr = false; \ No newline at end of file