12 Commits

15 changed files with 258 additions and 49 deletions

View File

@@ -7,6 +7,7 @@
"fill-labs.dependi", "fill-labs.dependi",
"mylesmurphy.prettify-ts", "mylesmurphy.prettify-ts",
"edwinhuish.better-comments-next", "edwinhuish.better-comments-next",
"dbaeumer.vscode-eslint" "dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss"
] ]
} }

View File

@@ -20,8 +20,8 @@ It enhances the osu! experience with quality-of-life features and integration sp
- [x] Shows the number of imported beatmapsets - [x] Shows the number of imported beatmapsets
- [x] Shows the number of imported skins - [x] Shows the number of imported skins
- [x] Displays ping to the EZPPFarm server - [x] Displays ping to the EZPPFarm server
- [ ] Discord Rich Presence (planned and in development) - [x] Discord Rich Presence
- [ ] Performance Display Overlay (planned) - [x] Performance Display Overlay (currently in experimental patcher release stream)
--- ---
@@ -31,9 +31,9 @@ It enhances the osu! experience with quality-of-life features and integration sp
| -------- | ---------------- | | -------- | ---------------- |
| Windows | ✅ Supported | | Windows | ✅ Supported |
| macOS | ❌ Not supported | | macOS | ❌ Not supported |
| Linux | ❌ Not supported | | Linux | 🕧 Partially supported (via osu-winello) |
> Currently, only **Windows** is supported. Support for other platforms may be considered in the future. > Currently, only **Windows** is fully supported. Support for other platforms may be considered in the future.
--- ---

View File

@@ -7,7 +7,7 @@
"@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": "^3.0.0", "@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",
@@ -158,7 +158,7 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@iarna/toml": ["@iarna/toml@3.0.0", "", {}, "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q=="], "@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="],
"@internationalized/date": ["@internationalized/date@3.8.2", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA=="], "@internationalized/date": ["@internationalized/date@3.8.2", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA=="],

View File

@@ -21,7 +21,7 @@
"@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": "^3.0.0", "@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",

2
src-tauri/Cargo.lock generated
View File

@@ -1094,7 +1094,7 @@ dependencies = [
[[package]] [[package]]
name = "ezpplauncher" name = "ezpplauncher"
version = "3.0.0-beta.3" version = "3.0.0-beta.5"
dependencies = [ dependencies = [
"discord-rich-presence", "discord-rich-presence",
"hardware-id", "hardware-id",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ezpplauncher" name = "ezpplauncher"
version = "3.0.0-beta.3" version = "3.0.0-beta.5"
description = "EZPPLauncher redefined." description = "EZPPLauncher redefined."
authors = ["HorizonCode"] authors = ["HorizonCode"]
edition = "2024" edition = "2024"

View File

@@ -14,7 +14,8 @@ use tokio::time::{Duration, sleep};
use crate::presence; use crate::presence;
use crate::utils::{ use crate::utils::{
check_folder_completeness, get_osu_config, get_osu_user_config, get_window_title_by_pid, check_folder_completeness, get_osu_config, get_osu_user_config, get_window_title_by_pid,
set_osu_config_vals, set_osu_user_config_vals, is_net8_installed, is_osuwinello_available, is_wmctrl_available, set_osu_config_vals,
set_osu_user_config_vals,
}; };
#[tauri::command] #[tauri::command]
@@ -282,8 +283,6 @@ pub fn set_osu_config_values(
#[tauri::command] #[tauri::command]
pub async fn run_osu_updater(folder: String) -> Result<(), String> { pub async fn run_osu_updater(folder: String) -> Result<(), String> {
let osu_exe_path = PathBuf::from(&folder).join("osu!.exe");
#[cfg(windows)] #[cfg(windows)]
const DETACHED_PROCESS: u32 = 0x00000008; const DETACHED_PROCESS: u32 = 0x00000008;
#[cfg(windows)] #[cfg(windows)]
@@ -292,6 +291,7 @@ pub async fn run_osu_updater(folder: String) -> Result<(), String> {
let mut updater_process = { let mut updater_process = {
#[cfg(windows)] #[cfg(windows)]
{ {
let osu_exe_path = PathBuf::from(&folder).join("osu!.exe");
Command::new(&osu_exe_path) Command::new(&osu_exe_path)
.arg("-repair") .arg("-repair")
.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP) .creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP)
@@ -301,7 +301,7 @@ pub async fn run_osu_updater(folder: String) -> Result<(), String> {
#[cfg(not(windows))] #[cfg(not(windows))]
{ {
Command::new(&osu_exe_path) Command::new("osu-wine")
.arg("-repair") .arg("-repair")
.spawn() .spawn()
.map_err(|e| format!("Failed to spawn updater: {}", e))? .map_err(|e| format!("Failed to spawn updater: {}", e))?
@@ -337,10 +337,8 @@ pub async fn run_osu_updater(folder: String) -> Result<(), String> {
sleep(Duration::from_millis(500)).await; sleep(Duration::from_millis(500)).await;
} }
// Wait for updater process to fully exit
let _ = updater_process.wait().await; let _ = updater_process.wait().await;
// Clean up update-related files
let force_update_files = [".require_update", "help.txt", "_pending"]; let force_update_files = [".require_update", "help.txt", "_pending"];
for update_file_name in &force_update_files { for update_file_name in &force_update_files {
let path = PathBuf::from(&folder).join(update_file_name); let path = PathBuf::from(&folder).join(update_file_name);
@@ -372,8 +370,6 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> {
/* #[cfg(windows)] /* #[cfg(windows)]
use std::os::windows::process::CommandExt; */ use std::os::windows::process::CommandExt; */
let osu_exe_path = PathBuf::from(&folder).join("osu!.exe");
#[cfg(windows)] #[cfg(windows)]
const DETACHED_PROCESS: u32 = 0x00000008; const DETACHED_PROCESS: u32 = 0x00000008;
#[cfg(windows)] #[cfg(windows)]
@@ -382,6 +378,7 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> {
let mut game_process = { let mut game_process = {
#[cfg(windows)] #[cfg(windows)]
{ {
let osu_exe_path = PathBuf::from(&folder).join("osu!.exe");
Command::new(&osu_exe_path) Command::new(&osu_exe_path)
.arg("-devserver") .arg("-devserver")
.arg("ez-pp.farm") .arg("ez-pp.farm")
@@ -392,8 +389,8 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> {
#[cfg(not(windows))] #[cfg(not(windows))]
{ {
Command::new(&osu_exe_path) Command::new("osu-wine")
.arg("-devserver") .arg("--devserver")
.arg("ez-pp.farm") .arg("ez-pp.farm")
.spawn() .spawn()
.map_err(|e| format!("Failed to spawn updater: {}", e))? .map_err(|e| format!("Failed to spawn updater: {}", e))?
@@ -416,13 +413,6 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> {
.spawn() .spawn()
.map_err(|e| format!("Failed to run patcher: {e}"))?; .map_err(|e| format!("Failed to run patcher: {e}"))?;
} }
#[cfg(not(windows))]
{
let _ = Command::new(&patcher_exe_path)
.spawn()
.map_err(|e| format!("Failed to run patcher: {e}"))?;
}
} }
} }
@@ -444,6 +434,7 @@ pub struct UpdateFile {
pub async fn get_ezpp_launcher_update_files( pub async fn get_ezpp_launcher_update_files(
folder: String, folder: String,
update_url: String, update_url: String,
update_stream: String,
) -> Result<(Vec<UpdateFile>, Vec<UpdateFile>), String> { ) -> Result<(Vec<UpdateFile>, Vec<UpdateFile>), String> {
let osu_path = PathBuf::from(folder); let osu_path = PathBuf::from(folder);
let client = Client::new(); let client = Client::new();
@@ -451,6 +442,7 @@ pub async fn get_ezpp_launcher_update_files(
let update_files = client let update_files = client
.patch(update_url) .patch(update_url)
.header("User-Agent", "EZPPLauncher") .header("User-Agent", "EZPPLauncher")
.query(&[("stream", update_stream)])
.send() .send()
.await .await
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
@@ -543,7 +535,6 @@ pub async fn download_ezpp_launcher_update_files(
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
// Emit progress to frontend
app.emit( app.emit(
"download-progress", "download-progress",
UpdateStatus { UpdateStatus {
@@ -713,3 +704,18 @@ pub async fn presence_update_user(user: PresenceUser) {
pub async fn presence_is_connected() -> bool { pub async fn presence_is_connected() -> bool {
presence::has_presence().await presence::has_presence().await
} }
#[tauri::command]
pub fn has_wmctrl() -> bool {
is_wmctrl_available()
}
#[tauri::command]
pub fn has_osuwinello() -> bool {
is_osuwinello_available()
}
#[tauri::command]
pub async fn has_net8() -> bool {
is_net8_installed().await
}

View File

@@ -10,7 +10,8 @@ use crate::commands::{
get_osu_release_stream, get_osu_skin, get_osu_version, get_platform, get_skins_count, get_osu_release_stream, get_osu_skin, get_osu_version, get_platform, get_skins_count,
is_osu_running, open_url_in_browser, presence_connect, presence_disconnect, is_osu_running, open_url_in_browser, presence_connect, presence_disconnect,
presence_is_connected, presence_update_status, presence_update_user, replace_ui_files, run_osu, presence_is_connected, presence_update_status, presence_update_user, replace_ui_files, run_osu,
run_osu_updater, set_osu_config_values, set_osu_user_config_values, valid_osu_folder, run_osu_updater, set_osu_config_values, set_osu_user_config_values, valid_osu_folder, has_osuwinello, has_wmctrl,
has_net8
}; };
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
@@ -60,7 +61,10 @@ pub fn run() {
presence_disconnect, presence_disconnect,
presence_update_status, presence_update_status,
presence_update_user, presence_update_user,
presence_is_connected presence_is_connected,
has_osuwinello,
has_wmctrl,
has_net8
]) ])
.plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())

View File

@@ -71,7 +71,6 @@ pub fn set_osu_user_config_vals(
} }
} }
// Collect indices and keys to update to avoid borrow checker issues
let mut updates = Vec::new(); let mut updates = Vec::new();
for (i, line) in lines.iter().enumerate() { for (i, line) in lines.iter().enumerate() {
if let Some((existing_key, _)) = line.split_once(" = ") { if let Some((existing_key, _)) = line.split_once(" = ") {
@@ -86,7 +85,6 @@ pub fn set_osu_user_config_vals(
keys_to_add.remove(trimmed_key.as_str()); keys_to_add.remove(trimmed_key.as_str());
} }
// Add new keys that were not found
for key in keys_to_add { for key in keys_to_add {
if let Some(value) = keys_to_set.get(key) { if let Some(value) = keys_to_set.get(key) {
lines.push(format!("{} = {}", key, value)); lines.push(format!("{} = {}", key, value));
@@ -139,7 +137,6 @@ pub fn set_osu_config_vals(
keys_to_add.remove(trimmed_key.as_str()); keys_to_add.remove(trimmed_key.as_str());
} }
// Add new keys that were not found
for key in keys_to_add { for key in keys_to_add {
if let Some(value) = keys_to_set.get(key) { if let Some(value) = keys_to_set.get(key) {
lines.push(format!("{} = {}", key, value)); lines.push(format!("{} = {}", key, value));
@@ -177,19 +174,78 @@ pub fn get_osu_config<P: AsRef<Path>>(
} }
#[cfg(not(windows))] #[cfg(not(windows))]
pub fn get_window_title_by_pid(_pid: Pid) -> String { pub fn is_osuwinello_available() -> bool {
"".to_string() use std::process::Command;
Command::new("osu-wine")
.arg("--info")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok()
}
#[cfg(windows)]
pub fn is_osuwinello_available() -> bool {
false
}
#[cfg(not(windows))]
pub fn is_wmctrl_available() -> bool {
use std::process::Command;
Command::new("wmctrl")
.arg("-V")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok()
}
#[cfg(windows)]
pub fn is_wmctrl_available() -> bool {
false
}
#[cfg(not(windows))]
pub fn get_window_title_by_pid(target_pid: Pid) -> String {
use std::process::Command;
let find_title = || -> Option<String> {
let output = Command::new("wmctrl").arg("-lp").output().ok()?;
if !output.status.success() {
return None;
}
let output_str = String::from_utf8(output.stdout).ok()?;
for line in output_str.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 4 {
continue;
}
if let Ok(pid) = parts[2].parse::<u32>() {
if pid == target_pid.as_u32() {
let title = parts[4..].join(" ");
return Some(title);
}
}
}
None
};
find_title().unwrap_or_default()
} }
#[cfg(windows)] #[cfg(windows)]
pub fn get_window_title_by_pid(pid: Pid) -> String { pub fn get_window_title_by_pid(pid: Pid) -> String {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use winapi::shared::windef::HWND; use winapi::shared::windef::HWND;
use winapi::um::winuser::{ use winapi::um::winuser::{
EnumWindows, GetWindowTextW, GetWindowThreadProcessId, IsWindowVisible, EnumWindows, GetWindowTextW, GetWindowThreadProcessId, IsWindowVisible,
}; };
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
extern "system" fn enum_windows_proc( extern "system" fn enum_windows_proc(
hwnd: HWND, hwnd: HWND,
@@ -210,11 +266,11 @@ pub fn get_window_title_by_pid(pid: Pid) -> String {
let title_str = title.to_string_lossy().into_owned(); let title_str = title.to_string_lossy().into_owned();
if !title_str.is_empty() { if !title_str.is_empty() {
*result.lock().unwrap() = Some(title_str); *result.lock().unwrap() = Some(title_str);
return 0; // Stop enumeration return 0;
} }
} }
} }
1 // Continue enumeration 1
} }
} }
@@ -228,3 +284,26 @@ pub fn get_window_title_by_pid(pid: Pid) -> String {
} }
result.lock().unwrap().clone().unwrap_or_default() result.lock().unwrap().clone().unwrap_or_default()
} }
pub async fn is_net8_installed() -> bool {
use std::process::Command;
let output_result = Command::new("dotnet").arg("--list-runtimes").output();
match output_result {
Ok(output) => {
if !output.status.success() {
eprintln!(
"Error: `dotnet --list-runtimes` failed with status: {}",
output.status
);
eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
return false;
}
let stdout_str = String::from_utf8_lossy(&output.stdout);
stdout_str
.lines()
.any(|line| line.starts_with("Microsoft.WindowsDesktop.App 8."))
}
Err(_) => false,
}
}

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "ezpplauncher", "productName": "ezpplauncher",
"version": "3.0.0-beta.2", "version": "3.0.0-beta.5",
"identifier": "farm.ezpp.launcher", "identifier": "farm.ezpp.launcher",
"build": { "build": {
"beforeDevCommand": "bun run vite:dev", "beforeDevCommand": "bun run vite:dev",

View File

@@ -6,9 +6,14 @@ import type { Release } from './types';
export const currentView = writable<Component>(Loading); export const currentView = writable<Component>(Loading);
export const platform = writable<string>("");
export const launcherVersion = writable<string>(''); export const launcherVersion = writable<string>('');
export const newVersion = writable<Release | undefined>(undefined); export const newVersion = writable<Release | undefined>(undefined);
export const launcherStreams = writable<string[]>(['stable']);
export const launcherStream = writable<string>('stable');
export const discordPresence = writable<boolean>(false); export const discordPresence = writable<boolean>(false);
export const presenceLoading = writable<boolean>(false); export const presenceLoading = writable<boolean>(false);

View File

@@ -1,8 +1,9 @@
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import type { UpdateFile, UpdateStatus } from './types'; import type { UpdateFile, UpdateStatus } from './types';
import { listen } from '@tauri-apps/api/event'; import { listen } from '@tauri-apps/api/event';
import { betterFetch } from '@better-fetch/fetch';
const updateUrl = 'https://ez-pp.farm/ezpplauncher'; const updateUrl = 'https://next.ez-pp.farm/api/ezpplauncher';
export const getHWID = async () => { export const getHWID = async () => {
const hwid = await invoke('get_hwid'); const hwid = await invoke('get_hwid');
@@ -73,8 +74,24 @@ export const runUpdater = async (folder: string) => await invoke('run_osu_update
export const runOsu = async (folder: string, patch: boolean) => export const runOsu = async (folder: string, patch: boolean) =>
await invoke('run_osu', { folder, patch }); await invoke('run_osu', { folder, patch });
export const getEZPPLauncherUpdateFiles = async (folder: string) => { export const getEZPPLauncherStreams = async () => {
const result = await invoke('get_ezpp_launcher_update_files', { folder, updateUrl }); const resp = await betterFetch<{ streams: string[] }>(updateUrl, {
method: 'POST',
});
if (!resp.error) {
return resp.data.streams;
}
return undefined;
};
export const getEZPPLauncherUpdateFiles = async (folder: string, updateStream: string) => {
const result = await invoke('get_ezpp_launcher_update_files', {
folder,
updateUrl,
updateStream,
});
if (typeof result === 'object') { if (typeof result === 'object') {
const [filesToDownload, updateFiles] = result as [UpdateFile[], UpdateFile[]]; const [filesToDownload, updateFiles] = result as [UpdateFile[], UpdateFile[]];
return { return {
@@ -116,3 +133,9 @@ export const exit = async () => await invoke('exit');
export const getPlatform = async () => await invoke<string>('get_platform'); export const getPlatform = async () => await invoke<string>('get_platform');
export const isOsuCorrupted = async (folder: string) => export const isOsuCorrupted = async (folder: string) =>
await invoke<boolean>('check_for_corruption', { folder }); await invoke<boolean>('check_for_corruption', { folder });
export const hasWMCTRL = async () =>
await invoke<boolean>('has_wmctrl');
export const hasOsuWinello = async () =>
await invoke<boolean>('has_osuwinello');
export const hasNet8 = async () =>
await invoke<boolean>('has_net8');

View File

@@ -9,11 +9,14 @@
currentSkin, currentSkin,
currentView, currentView,
discordPresence, discordPresence,
launcherStream,
launcherStreams,
launcherVersion, launcherVersion,
launching, launching,
newVersion, newVersion,
osuBuild, osuBuild,
osuStream, osuStream,
platform,
presenceLoading, presenceLoading,
serverConnectionFails, serverConnectionFails,
serverPing, serverPing,
@@ -87,6 +90,9 @@
getSkin, getSkin,
getSkinsCount, getSkinsCount,
getVersion, getVersion,
hasNet8,
hasOsuWinello,
hasWMCTRL,
isOsuCorrupted, isOsuCorrupted,
isOsuRunning, isOsuRunning,
isValidOsuFolder, isValidOsuFolder,
@@ -184,9 +190,26 @@
return; return;
} }
if ($platform === 'linux') {
if (!(await hasWMCTRL())) {
toast.error('Hmmm...', {
description: 'wmctrl seems to be missing, please install via AUR.',
});
launching.set(false);
return;
}
if (!(await hasOsuWinello())) {
toast.error('Hmmm...', {
description: 'osu-winello seems to be missing, please install it.',
});
launching.set(false);
return;
}
}
try { try {
launchInfo = 'Looking for EZPPLauncher File updates...'; launchInfo = 'Looking for EZPPLauncher File updates...';
const updateResult = await getEZPPLauncherUpdateFiles(osuPath); const updateResult = await getEZPPLauncherUpdateFiles(osuPath, $launcherStream);
if (updateResult) { if (updateResult) {
if (updateResult.filesToDownload.length > 0) { if (updateResult.filesToDownload.length > 0) {
@@ -802,7 +825,7 @@
</div> </div>
<div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6"> <div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6">
<div <div
class="flex flex-row flex-nowrap h-11 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 <button
class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab === class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab ===
@@ -1028,11 +1051,18 @@
> >
<div class="flex flex-col"> <div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">Patching</Label> <Label class="text-sm" for="setting-custom-cursor">Patching</Label>
<div class="text-muted-foreground text-xs">Shows misses in Relax and Autopilot</div> <div class="text-muted-foreground text-xs">
Shows misses in Relax and Autopilot {#if $platform !== 'windows'}<span
class="text-red-500 bg-red-800/20 border border-red-600/20 p-0.5 mx-1 px-2 rounded-lg"
>currently only on windows!</span
>
{/if}
</div>
</div> </div>
<Checkbox <Checkbox
id="setting-custom-cursor" id="setting-custom-cursor"
checked={$patch} checked={$platform === 'windows' ? $patch : false}
disabled={$platform !== 'windows'}
onCheckedChange={async (e) => { onCheckedChange={async (e) => {
patch.set(e); patch.set(e);
$userSettings.save(); $userSettings.save();
@@ -1115,7 +1145,7 @@
</div> </div>
</div> </div>
<div <div
class="grid grid-cols-[0.7fr_auto] gap-y-5 items-center border-theme-800 pl-6 pr-5 pb-4" class="grid grid-cols-[0.7fr_auto] gap-y-1 items-center border-theme-800 pl-6 pr-5 pb-4"
> >
<div class="flex flex-col"> <div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">osu! installation path</Label> <Label class="text-sm" for="setting-custom-cursor">osu! installation path</Label>
@@ -1135,6 +1165,54 @@
onclick={browse_osu_installation}>Browse</Button onclick={browse_osu_installation}>Browse</Button
> >
</div> </div>
<div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">patcher release stream</Label>
<div class="text-muted-foreground text-xs">
test different versions of the patcher
</div>
</div>
<div class="flex flex-row w-full">
<Select.Root
type="single"
bind:value={$launcherStream}
onValueChange={async (newStream) => {
if (newStream === 'experimental' && !(await hasNet8())) {
launcherStream.set('stable');
toast.error('.NET 8.0 Desktop Runtime not found!', {
action: {
label: 'Download .NET 8.0',
onClick: async () =>
await openURL(
'https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.18-windows-x64-installer'
),
},
});
return;
}
$userSettings.value('patcherStream').set(newStream);
launcherStream.set(newStream);
await $userSettings.save();
}}
>
<Select.Trigger
class="border-theme-800 bg-theme-950 !text-muted-foreground font-semibold"
>
<div class="flex flex-row items-center gap-2 font-normal text-foreground">
{$launcherStream}
</div>
</Select.Trigger>
<Select.Content class="bg-theme-950 border border-theme-950 rounded-lg">
{#each $launcherStreams as stream (stream)}
<Select.Item value={stream}>
<div class="flex flex-row gap-2 items-center">
{stream}
</div>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -7,6 +7,8 @@
currentSkin, currentSkin,
currentView, currentView,
firstStartup, firstStartup,
launcherStream,
launcherStreams,
launcherVersion, launcherVersion,
newVersion, newVersion,
osuBuild, osuBuild,
@@ -30,6 +32,7 @@
import { currentUserInfo } from '@/data'; import { currentUserInfo } from '@/data';
import { import {
getBeatmapSetsCount, getBeatmapSetsCount,
getEZPPLauncherStreams,
getReleaseStream, getReleaseStream,
getSkin, getSkin,
getSkinsCount, getSkinsCount,
@@ -148,6 +151,14 @@
newVersion.set(launcherUpdate); newVersion.set(launcherUpdate);
} }
const ezpplauncherStreams = await getEZPPLauncherStreams();
if (ezpplauncherStreams) launcherStreams.set(ezpplauncherStreams);
const selectedLauncherStream = $userSettings.value('patcherStream').get('stable');
if ($launcherStreams.includes(selectedLauncherStream)) {
launcherStream.set(selectedLauncherStream);
}
animate(ezppLogo, { animate(ezppLogo, {
opacity: [1, 0], opacity: [1, 0],
scale: [1, 1.05], scale: [1, 1.05],

View File

@@ -9,6 +9,7 @@
discordPresence, discordPresence,
firstStartup, firstStartup,
launcherVersion, launcherVersion,
platform,
presenceLoading, presenceLoading,
setupValues, setupValues,
} from '@/global'; } from '@/global';
@@ -90,7 +91,8 @@
disableReload(); disableReload();
setupValues(); setupValues();
launcherVersion.set(await getLauncherVersion()); launcherVersion.set(await getLauncherVersion());
if ((await getPlatform()) !== 'windows') unsupported_platform = true; platform.set(await getPlatform());
if ($platform !== "windows" && $platform !== "linux") unsupported_platform = true;
const isFirstStartup = await $userSettings.init(); const isFirstStartup = await $userSettings.init();
$userAuth.init(); $userAuth.init();