use sha2::{Digest, Sha512}; use std::fs::{remove_dir_all}; use std::io::Cursor; use std::{ fs::{File, create_dir_all}, io::{copy}, path::PathBuf, process::Command }; use tauri::{AppHandle, Manager}; use tauri_plugin_os::platform; use zip::ZipArchive; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; fn should_skip(name: &str) -> bool { name.starts_with("__MACOSX/") || name == "__MACOSX" || name.ends_with("/.DS_Store") || name.ends_with(".DS_Store") } async fn unzip_to_dir(bytes: &[u8], target: &PathBuf) -> String { let bytes = bytes.to_vec(); let target = target.clone(); let res = tauri::async_runtime::spawn_blocking(move || -> Result<(), String> { let reader = Cursor::new(bytes); let mut archive = ZipArchive::new(reader).map_err(|e| e.to_string())?; for i in 0..archive.len() { let mut entry = archive.by_index(i).map_err(|e| e.to_string())?; let name = entry.name(); if should_skip(name) { continue; } let outpath = target.join(name); if entry.is_dir() { create_dir_all(&outpath).map_err(|e| e.to_string())?; #[cfg(unix)] if let Some(mode) = entry.unix_mode() { std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode)) .map_err(|e| e.to_string())?; } } else { if let Some(parent) = outpath.parent() { create_dir_all(parent).map_err(|e| e.to_string())?; } let mut outfile = File::create(&outpath).map_err(|e| e.to_string())?; copy(&mut entry, &mut outfile).map_err(|e| e.to_string())?; #[cfg(unix)] if let Some(mode) = entry.unix_mode() { std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode)) .map_err(|e| e.to_string())?; } } } Ok(()) }) .await; match res { Ok(Ok(())) => "1".into(), _ => "-1".into(), } } fn get_sha512_hash(data: &[u8]) -> String { let mut hasher = Sha512::new(); hasher.update(data); let hash = hasher.finalize(); format!("{:x}", hash) } #[tauri::command] async fn download(app: AppHandle, url: String, hash: String) -> String { let client = reqwest::Client::new(); let resp = match client.get(&url).send().await { Ok(r) => r, Err(_) => return "-1".to_string(), }; let bytes = match resp.bytes().await { Ok(b) => b, Err(_) => return "-1".to_string(), }; let download_hash = get_sha512_hash(&bytes); if hash != download_hash { return "-2".to_string(); } let bin_path = match app.path().app_local_data_dir() { Ok(p) => p.join("bin"), Err(_) => return "-1".to_string(), }; let _ = tokio::fs::create_dir_all(&bin_path).await; let unzip_res = unzip_to_dir(&bytes, &bin_path).await; if unzip_res == "-1" { return "-1".to_string(); } drop(bytes); return "1".to_string(); } #[allow(unused_variables)] #[tauri::command] fn load(app: AppHandle) { let bin_path = match app.path().app_local_data_dir() { Ok(p) => p.join("bin"), Err(_) => return, }; if !bin_path.exists() { return; } if platform() == "macos" { if let Err(_) = Command::new("open") .arg("Lncvrt Games Launcher.app") .current_dir(&bin_path) .spawn() { eprintln!("Failed to launch game on macOS"); } } else if platform() == "linux" { if let Err(_) = Command::new("./lncvrt-games-launcher") .current_dir(&bin_path) .spawn() { eprintln!("Failed to launch game on macOS"); } } else if platform() == "windows" { if let Err(_) = Command::new(&bin_path.join("lncvrt-games-launcher.exe")) .current_dir(&bin_path) .spawn() { eprintln!("Failed to launch game on macOS"); } } app.exit(0); } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .setup(|app| { let app_local_data_dir = match app.path().app_local_data_dir() { Ok(p) => p, Err(_) => return Ok(()), }; let downloads_dir = app_local_data_dir.join("downloads"); let updates_dir = app_local_data_dir.join("updates"); let bin_dir = app_local_data_dir.join("bin"); let version_file = app_local_data_dir.join(".version"); if downloads_dir.exists() { let _ = remove_dir_all(downloads_dir); } if updates_dir.exists() { let _ = remove_dir_all(&updates_dir); } if bin_dir.exists() && bin_dir.is_file() { let _ = remove_dir_all(&bin_dir); } if version_file.exists() && !bin_dir.is_file() { let _ = remove_dir_all(&version_file); } Ok(()) }) .plugin(tauri_plugin_window_state::Builder::new().build()) .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { let _ = app .get_webview_window("main") .expect("no main window") .set_focus(); })) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_fs::init()) .invoke_handler(tauri::generate_handler![download, load]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }