From e3be6078ffd022cb617bfd950a801350ca8f1a33 Mon Sep 17 00:00:00 2001 From: neoapps-dev Date: Sun, 16 Aug 2026 23:39:06 +0300 Subject: [PATCH] feat: Intel macOS Support! --- src-tauri/src/commands/game.rs | 12 +- src-tauri/src/commands/macos_setup.rs | 209 +++++++++++++++++++++++++- src-tauri/src/platform/macos.rs | 7 +- 3 files changed, 216 insertions(+), 12 deletions(-) diff --git a/src-tauri/src/commands/game.rs b/src-tauri/src/commands/game.rs index 6fc9ba3..89073c9 100644 --- a/src-tauri/src/commands/game.rs +++ b/src-tauri/src/commands/game.rs @@ -201,9 +201,15 @@ async fn launch_game_desktop( let gptk_no_hud = macos::find_executable_recursive(&toolkit_dir, "gameportingtoolkit-no-hud") .or_else(|| macos::find_executable_recursive(&toolkit_dir, "gameportingtoolkit")); - let wine_binary = macos::find_executable_recursive(&toolkit_dir, "wine64") - .or_else(|| macos::find_executable_recursive(&toolkit_dir, "wine")) - .ok_or_else(|| "Unable to locate wine binary inside runtime.".to_string())?; + let is_intel = std::env::consts::ARCH == "x86_64"; + let wine_binary = if is_intel { + macos::find_executable_recursive(&toolkit_dir, "wine") + .or_else(|| macos::find_executable_recursive(&toolkit_dir, "wine64")) + } else { + macos::find_executable_recursive(&toolkit_dir, "wine64") + .or_else(|| macos::find_executable_recursive(&toolkit_dir, "wine")) + } + .ok_or_else(|| "Unable to locate wine binary inside runtime.".to_string())?; let wine_bin_dir = wine_binary .parent() diff --git a/src-tauri/src/commands/macos_setup.rs b/src-tauri/src/commands/macos_setup.rs index 75a3e5c..1dee3e3 100644 --- a/src-tauri/src/commands/macos_setup.rs +++ b/src-tauri/src/commands/macos_setup.rs @@ -1,5 +1,4 @@ -//neo: TODO: Intel macOS support - +//neo: thanks Huckle for the Intel macOS support reference code lol use tauri::AppHandle; #[cfg(target_os = "macos")] use std::io::Write; @@ -57,6 +56,14 @@ pub async fn setup_macos_runtime(window: tauri::Window, app: AppHandle) -> Resul assets: Vec, } + let is_intel = std::env::consts::ARCH == "x86_64"; + + let repo = if is_intel { + "Gcenx/macOS_Wine_builds" + } else { + "Gcenx/game-porting-toolkit" + }; + macos::emit_macos_setup_progress( &window, "resolving", @@ -66,7 +73,7 @@ pub async fn setup_macos_runtime(window: tauri::Window, app: AppHandle) -> Resul let client = reqwest::Client::new(); let release_text = client - .get("https://api.github.com/repos/Gcenx/game-porting-toolkit/releases/latest") + .get(format!("https://api.github.com/repos/{}/releases/latest", repo)) .header("User-Agent", "Emerald-Legacy-Launcher") .send() .await @@ -82,7 +89,15 @@ pub async fn setup_macos_runtime(window: tauri::Window, app: AppHandle) -> Resul let asset = release .assets .iter() - .find(|a| a.name.ends_with(".tar.xz") || a.name.ends_with(".tar.gz")) + .find(|a| { + let is_archive = + a.name.ends_with(".tar.xz") || a.name.ends_with(".tar.gz"); + if is_intel { + is_archive && a.name.contains("staging") + } else { + is_archive + } + }) .ok_or_else(|| "No compatible runtime asset found in latest release.".to_string())?; let runtime_dir = macos::get_macos_runtime_dir(&app); @@ -173,9 +188,14 @@ pub async fn setup_macos_runtime(window: tauri::Window, app: AppHandle) -> Resul } fs::create_dir_all(&prefix_dir).map_err(|e| e.to_string())?; - let wine_binary = macos::find_executable_recursive(&toolkit_dir, "wine64") - .or_else(|| macos::find_executable_recursive(&toolkit_dir, "wine")) - .ok_or_else(|| "Unable to locate wine binary inside runtime.".to_string())?; + let wine_binary = if is_intel { + macos::find_executable_recursive(&toolkit_dir, "wine") + .or_else(|| macos::find_executable_recursive(&toolkit_dir, "wine64")) + } else { + macos::find_executable_recursive(&toolkit_dir, "wine64") + .or_else(|| macos::find_executable_recursive(&toolkit_dir, "wine")) + } + .ok_or_else(|| "Unable to locate wine binary inside runtime.".to_string())?; let wine_bin_dir = wine_binary .parent() @@ -215,6 +235,181 @@ pub async fn setup_macos_runtime(window: tauri::Window, app: AppHandle) -> Resul return Err("Wine prefix initialization failed".into()); } + if is_intel { + macos::emit_macos_setup_progress( + &window, + "dxvk", + "Fetching latest DXVK…".into(), + None, + ); + + #[derive(Deserialize)] + struct DxvkAsset { + name: String, + browser_download_url: String, + } + + #[derive(Deserialize)] + struct DxvkRelease { + assets: Vec, + } + + let dxvk_release_text = client + .get("https://api.github.com/repos/Gcenx/DXVK-macOS/releases/latest") + .header("User-Agent", "Emerald-Legacy-Launcher") + .send() + .await + .map_err(|e| e.to_string())? + .error_for_status() + .map_err(|e| e.to_string())? + .text() + .await + .map_err(|e| e.to_string())?; + + let dxvk_release: DxvkRelease = + serde_json::from_str(&dxvk_release_text).map_err(|e| e.to_string())?; + let dxvk_asset = dxvk_release + .assets + .iter() + .find(|a| { + let is_archive = + a.name.ends_with(".tar.xz") || a.name.ends_with(".tar.gz"); + is_archive && !a.name.contains("builtin") + }) + .ok_or_else(|| "No compatible DXVK asset found.".to_string())?; + + macos::emit_macos_setup_progress( + &window, + "dxvk_downloading", + "Downloading DXVK…".into(), + Some(0.0), + ); + + let dxvk_archive_path = runtime_dir.join(format!("dxvk_{}", dxvk_asset.name)); + let dxvk_response = client + .get(&dxvk_asset.browser_download_url) + .header("User-Agent", "Emerald-Legacy-Launcher") + .send() + .await + .map_err(|e| e.to_string())? + .error_for_status() + .map_err(|e| e.to_string())?; + + let dxvk_total = dxvk_response.content_length().unwrap_or(0) as f64; + let mut dxvk_file = fs::File::create(&dxvk_archive_path).map_err(|e| e.to_string())?; + let mut dxvk_downloaded = 0.0; + let mut dxvk_last_percent: i64 = -1; + let mut dxvk_stream = dxvk_response.bytes_stream(); + while let Some(chunk) = dxvk_stream.next().await { + let chunk = chunk.map_err(|e| e.to_string())?; + dxvk_file.write_all(&chunk).map_err(|e| e.to_string())?; + dxvk_downloaded += chunk.len() as f64; + if dxvk_total > 0.0 { + let percent = (dxvk_downloaded / dxvk_total * 100.0).clamp(0.0, 100.0); + let rounded = percent.floor() as i64; + if rounded != dxvk_last_percent { + dxvk_last_percent = rounded; + macos::emit_macos_setup_progress( + &window, + "dxvk_downloading", + format!("Downloading DXVK… {}%", rounded), + Some(percent), + ); + } + } + } + drop(dxvk_file); + macos::emit_macos_setup_progress( + &window, + "dxvk_extracting", + "Extracting DXVK…".into(), + None, + ); + + let dxvk_dir = runtime_dir.join("dxvk"); + fs::create_dir_all(&dxvk_dir).map_err(|e| e.to_string())?; + let status = Command::new("tar") + .args([ + "-xf", + dxvk_archive_path + .to_str() + .ok_or_else(|| "Invalid DXVK archive path".to_string())?, + "-C", + dxvk_dir + .to_str() + .ok_or_else(|| "Invalid DXVK extraction path".to_string())?, + ]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .status() + .map_err(|e| e.to_string())?; + let _ = fs::remove_file(&dxvk_archive_path); + if !status.success() { + return Err(format!("DXVK extraction failed with status: {:?}", status)); + } + + macos::emit_macos_setup_progress( + &window, + "dxvk_patching", + "Patching runtime with DXVK…".into(), + None, + ); + + let system32 = prefix_dir.join("drive_c/windows/system32"); + let syswow64 = prefix_dir.join("drive_c/windows/syswow64"); + let _ = fs::create_dir_all(&system32); + let _ = fs::create_dir_all(&syswow64); + let copy_dir = |src_dir: &std::path::Path, dst_dir: &std::path::Path| -> Result<(), String> { + if !src_dir.exists() { + return Ok(()); + } + for entry in fs::read_dir(src_dir).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + let path = entry.path(); + if path.extension().and_then(|e| e.to_str()) == Some("dll") { + let dest = dst_dir.join(path.file_name().unwrap()); + fs::copy(&path, &dest).map_err(|e| e.to_string())?; + } + } + Ok(()) + }; + + let dxvk_x64 = dxvk_dir.join("x64"); + let dxvk_x32 = dxvk_dir.join("x32"); + copy_dir(&dxvk_x64, &system32)?; + copy_dir(&dxvk_x32, &syswow64)?; + for dll_name in &["d3d11", "dxgi"] { + let mut reg_cmd = Command::new(&wine_binary); + reg_cmd + .args(["reg", "add", "HKCU\\Software\\Wine\\DllOverrides"]) + .arg("/v") + .arg(dll_name) + .arg("/t") + .arg("REG_SZ") + .arg("/d") + .arg("native,builtin") + .arg("/f"); + reg_cmd + .env("WINEPREFIX", &prefix_dir) + .env("WINEDEBUG", "-all") + .env( + "PATH", + format!( + "{}:{}", + wine_bin_dir.to_string_lossy(), + std::env::var("PATH").unwrap_or_default() + ), + ) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + let _ = reg_cmd.status(); + } + + let _ = fs::remove_dir_all(&dxvk_dir); + } + macos::emit_macos_setup_progress(&window, "done", "Setup complete.".into(), Some(100.0)); Ok(()) } diff --git a/src-tauri/src/platform/macos.rs b/src-tauri/src/platform/macos.rs index 8a3ff64..c97ab93 100644 --- a/src-tauri/src/platform/macos.rs +++ b/src-tauri/src/platform/macos.rs @@ -50,6 +50,9 @@ pub fn is_macos_runtime_installed(app: &AppHandle) -> bool { if !toolkit_dir.exists() { return false; } - let candidates = ["Game Porting Toolkit.app"]; - candidates.iter().any(|name| find_executable_recursive(&toolkit_dir, name).is_some()) + if find_executable_recursive(&toolkit_dir, "Game Porting Toolkit.app").is_some() { + return true; + } + find_executable_recursive(&toolkit_dir, "wine").is_some() + || find_executable_recursive(&toolkit_dir, "wine64").is_some() }