feat: Intel macOS Support!

This commit is contained in:
neoapps-dev 2026-08-16 23:39:06 +03:00
parent a844ebb12a
commit e3be6078ff
3 changed files with 216 additions and 12 deletions

View file

@ -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()

View file

@ -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<GithubAsset>,
}
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<DxvkAsset>,
}
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(())
}

View file

@ -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()
}