From 1834d8dfb38a6edb2534ac0a2ee493215f0a3453 Mon Sep 17 00:00:00 2001 From: HorizonCode Date: Mon, 30 Jun 2025 23:07:45 +0200 Subject: [PATCH] feat: implement osu! installation validation --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/lib.rs | 124 ++++++++++++++++++++++- src-tauri/src/utils.rs | 24 +++++ src/lib/api/ezpp.ts | 41 ++++++++ src/lib/config.ts | 10 +- src/lib/global.ts | 4 + src/pages/Loading.svelte | 78 ++++++++++++++- src/pages/Login.svelte | 28 ++++++ src/pages/SetupWizard.svelte | 189 +++++++++++++++++++++++++++++------ src/routes/+layout.svelte | 12 ++- src/routes/+page.svelte | 8 +- 12 files changed, 476 insertions(+), 44 deletions(-) create mode 100644 src-tauri/src/utils.rs create mode 100644 src/pages/Login.svelte diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f733987..79a881e 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1084,6 +1084,7 @@ dependencies = [ "tauri-plugin-shell", "tauri-plugin-single-instance", "tauri-plugin-sql", + "winreg 0.55.0", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index d124217..8bbedf8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -27,6 +27,7 @@ tauri-plugin-sql = "2.3.0" tauri-plugin-dialog = "2.3.0" tauri-plugin-fs = "2.4.0" hardware-id = "0.3.0" +winreg = "0.55.0" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-single-instance = "2.3.0" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4131b28..c7694c5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,6 +1,12 @@ // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ use hardware_id::get_id; +use std::path::PathBuf; use tauri::Manager; +use winreg::enums::*; +use winreg::RegKey; + +use crate::utils::check_folder_completeness; +mod utils; #[tauri::command] fn get_hwid() -> String { @@ -8,6 +14,122 @@ fn get_hwid() -> String { hwid.into() } +#[tauri::command(rename_all = "snake_case")] +fn valid_osu_folder(folder: String) -> bool { + // List of files that should be present in the osu! installation folder + let osu_folder_files = [ + "avcodec-51.dll", + "avformat-52.dll", + "avutil-49.dll", + "bass.dll", + "bass_fx.dll", + "collection.db", + "d3dcompiler_47.dll", + "libEGL.dll", + "libGLESv2.dll", + "Microsoft.Ink.dll", + "OpenTK.dll", + "osu!.cfg", + "osu!.db", + "osu!.exe", + "osu!auth.dll", + "osu!gameplay.dll", + "osu!seasonal.dll", + "osu!ui.dll", + "presence.db", + "pthreadGC2.dll", + "scores.db", + ]; + + let path = PathBuf::from(folder); + let match_percentage = check_folder_completeness(path, &osu_folder_files) >= 70.0; + if match_percentage { + return true; + } + return false; +} + +#[tauri::command] +fn find_osu_installation() -> Option { + // List of possible registry paths to check for osu! installation + let hklm_registry_paths = ["SOFTWARE\\Classes\\osu\\DefaultIcon"]; + + let hkcr_registry_paths = [ + "osustable.File.osk\\DefaultIcon", + "osustable.File.osr\\DefaultIcon", + "osustable.File.osz\\DefaultIcon", + ]; + + let osu_folder_files = [ + "avcodec-51.dll", + "avformat-52.dll", + "avutil-49.dll", + "bass.dll", + "bass_fx.dll", + "collection.db", + "d3dcompiler_47.dll", + "libEGL.dll", + "libGLESv2.dll", + "Microsoft.Ink.dll", + "OpenTK.dll", + "osu!.cfg", + "osu!.db", + "osu!.exe", + "osu!auth.dll", + "osu!gameplay.dll", + "osu!seasonal.dll", + "osu!ui.dll", + "presence.db", + "pthreadGC2.dll", + "scores.db", + ]; + + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + + for reg_path in &hklm_registry_paths { + if let Ok(subkey) = hklm.open_subkey_with_flags(reg_path, KEY_READ | KEY_WOW64_32KEY) { + let value_names = [""]; + for value_name in &value_names { + if let Ok(value) = subkey.get_value::(value_name) { + let trimmed = value.trim_matches('"'); + let stripped = trimmed.strip_suffix(",0").unwrap_or(trimmed); + let path = PathBuf::from(stripped.trim()); + if let Some(parent) = path.parent() { + let match_percentage = check_folder_completeness(parent, &osu_folder_files); + + if match_percentage >= 70.0 { + return Some(parent.to_string_lossy().into()); + } + } + } + } + } + } + + let hkcr = RegKey::predef(HKEY_CLASSES_ROOT); + + for reg_path in &hkcr_registry_paths { + if let Ok(subkey) = hkcr.open_subkey_with_flags(reg_path, KEY_READ | KEY_WOW64_32KEY) { + let value_names = [""]; + for value_name in &value_names { + if let Ok(value) = subkey.get_value::(value_name) { + let trimmed = value.trim_matches('"'); + let stripped = trimmed.strip_suffix(",1").unwrap_or(trimmed); + let path = PathBuf::from(stripped.trim()); + if let Some(parent) = path.parent() { + let match_percentage = check_folder_completeness(parent, &osu_folder_files); + + if match_percentage >= 70.0 { + return Some(parent.to_string_lossy().into()); + } + } + } + } + } + } + return None; +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let mut builder = tauri::Builder::default().plugin(tauri_plugin_fs::init()); @@ -23,7 +145,7 @@ pub fn run() { } builder - .invoke_handler(tauri::generate_handler![get_hwid]) + .invoke_handler(tauri::generate_handler![get_hwid, find_osu_installation, valid_osu_folder]) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_sql::Builder::default().build()) diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs new file mode 100644 index 0000000..a40cc89 --- /dev/null +++ b/src-tauri/src/utils.rs @@ -0,0 +1,24 @@ +use std::path::Path; + +/// Checks the presence of required files in a folder and returns the percentage found. +/// +/// # Arguments +/// * `folder_path` - The path to the folder to check. +/// * `required_files` - A slice of file names that should be present in the folder. +/// +/// # Returns +/// * `f32` - The percentage (0.0 to 100.0) of required files found in the folder. +pub fn check_folder_completeness>(folder_path: P, required_files: &[&str]) -> f32 { + let mut found = 0; + for file in required_files { + let file_path = folder_path.as_ref().join(file); + if file_path.exists() { + found += 1; + } + } + if required_files.is_empty() { + 100.0 + } else { + (found as f32 / required_files.len() as f32) * 100.0 + } +} \ No newline at end of file diff --git a/src/lib/api/ezpp.ts b/src/lib/api/ezpp.ts index 630332e..26cac53 100644 --- a/src/lib/api/ezpp.ts +++ b/src/lib/api/ezpp.ts @@ -1,6 +1,7 @@ import ky from 'ky'; const BANCHO_ENDPOINT = 'https://c.ez-pp.farm/'; +const ENDPOINT = 'https://ez-pp.farm/'; export const ezppfarm = { ping: async (): Promise => { @@ -14,4 +15,44 @@ export const ezppfarm = { return undefined; } }, + login: async ( + username: string, + password: string + ): Promise< + | { + code: number; + message: string; + user?: { + id: number; + donor: boolean; + name: string; + email: string; + }; + } + | undefined + > => { + try { + const request = await ky(`${ENDPOINT}login/check`, { + method: 'POST', + body: JSON.stringify({ username, password }), + headers: { + 'Content-Type': 'application/json', + 'User-Agent': 'EZPPLauncher', + }, + }); + if (!request.ok) return undefined; + return await request.json<{ + code: number; + message: string; + user?: { + id: number; + donor: boolean; + name: string; + email: string; + }; + }>(); + } catch { + return undefined; + } + }, }; diff --git a/src/lib/config.ts b/src/lib/config.ts index 2f012bf..dc9ce75 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -16,7 +16,7 @@ export class Config { private crypto: Crypto | undefined; private configFilePath: string | undefined; - async init() { + async init(): Promise { const hwid: string = (await invoke('get_hwid')) ?? 'recorderinsandybridge'; this.crypto = new Crypto(hwid); @@ -28,9 +28,11 @@ export class Config { 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(); + const configExists = await exists(this.configFilePath); + if (!configExists) return true; + + await this.load(); + return false; } private async load() { diff --git a/src/lib/global.ts b/src/lib/global.ts index f9e8066..bdec845 100644 --- a/src/lib/global.ts +++ b/src/lib/global.ts @@ -1,5 +1,9 @@ import { writable } from 'svelte/store'; import { ezppfarm } from './api/ezpp'; +import type { Component } from 'svelte'; +import Loading from '../pages/Loading.svelte'; + +export const current_view = writable(Loading); export const server_ping = writable(undefined); export const server_connection_fails = writable(0); diff --git a/src/pages/Loading.svelte b/src/pages/Loading.svelte index 9f57cf6..bc57ab2 100644 --- a/src/pages/Loading.svelte +++ b/src/pages/Loading.svelte @@ -1,8 +1,78 @@ +
- - -
\ No newline at end of file +
+ + + + EZPPLauncher Logo +
+ diff --git a/src/pages/Login.svelte b/src/pages/Login.svelte new file mode 100644 index 0000000..4c4344c --- /dev/null +++ b/src/pages/Login.svelte @@ -0,0 +1,28 @@ + diff --git a/src/pages/SetupWizard.svelte b/src/pages/SetupWizard.svelte index 9139a35..c69f24d 100644 --- a/src/pages/SetupWizard.svelte +++ b/src/pages/SetupWizard.svelte @@ -1,25 +1,73 @@
@@ -28,27 +76,109 @@
i + ? 'border-green-800/30 bg-green-900/30' + : 'border-theme-800 bg-theme-900'} rounded-lg p-2 transition-all" >
- {i + 1} + {#if selectedStep > i + 1} + + {:else} + {i + 1} + {/if}
{step}{step}
{/each}
-
- {#if selectedStep === 1} -

Welcome to EZPPLauncher!

- - {/if} -
+ {#if selectedStep === 1} +
+ EZPPLauncher Logo +

Welcome to EZPPLauncher!

+

+ This setup wizard will guide you through the initial setup of EZPPLauncher. +

+
+ Please make sure you have osu! installed on your system before proceeding. +
+
+ {:else if selectedStep === 2} +
+

Locate your osu! Installation

+

+ Please select the folder where your osu! installation is located. +

+
+ + +
+ {#if !manualSelect} + {#if autoDetectedOsuPath} +
+ + Auto-detected osu! installation path! Please check if its correct! +
+ {:else} +
+ + Could not auto-detect osu! installation path. Please select it manually. +
+ {/if} + {:else if manualSelectValid} +
+ + Selected osu! installation path is valid! +
+ {:else} +
+ + Selected osu! installation path is invalid! Please select a valid osu! installation. +
+ {/if} +
+ {/if} +
= steps.length || + (selectedStep === 2 && osuInstallationPath.length <= 0)}>Next
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d3a97b3..b69884b 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,17 +1,19 @@ diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e3eaf31..bfd7bb8 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,7 @@ - \ No newline at end of file +