From 7745bed0e7981dec23d4981b1c43c15d9769cfed Mon Sep 17 00:00:00 2001 From: Avery Felts Date: Fri, 31 Jul 2026 15:39:05 -0600 Subject: [PATCH 01/14] replace setup bundles with complete runtime installer --- AGENTS.md | 21 ++- app/package.json | 4 + app/src-rust/src/installer.rs | 142 +++++++++++++++----- app/src-rust/src/setup.rs | 15 ++- app/src/renderer/components/SetupWizard.vue | 2 +- 5 files changed, 141 insertions(+), 43 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 64450c225..6d366d4d4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,27 @@ # AGENTS.md -**Updated:** 2026-07-08 +**Updated:** 2026-07-31 Guide for AI agents working on the MetalSharp repository. +## 0.60.0 Preview Release Work + +Development is isolated on `agent/0.60-preview-release`. The saved phase plan is +`~/Documents/obsidian/Avery'sVault/0.60.0 Official Preview Release Plan.md`. + +- Phase 1 replaces first-run split-bundle installation with + `scripts/install-metalsharp-wine-runtime.sh` from release + `v0.60.0-dependency-bundles`. +- The canonical complete-runtime SHA-256 is + `93a456a40a7bf0ad2fecace5c01c58a366f85cc2901f6f8780c056c9e3b256ee`. +- Runtime completion is gated by `.metalsharp-runtime-install` plus Wine, + wineserver, fonts/NLS, DXVK i386, vkd3d-proton D3D12, OpenGL-Metal, and + MoltenVK payloads. Do not restore the old six-archive setup gate. +- The runtime installs transactionally at `~/.metalsharp/runtime`; prefixes, + Steam, bottles, saves, and shader caches remain outside that directory. +- Phase 1 keeps Rosetta installation until macOS 28 and keeps GPTK/Homebrew as + a separate route. The complete runtime launch adapters explicitly disable + FEX TSO, vector TSO, and memcpy/set TSO. + ## What This Project Is MetalSharp is a macOS app that runs Windows Steam games and Windows programs via Wine + Metal translation. It's an Electron app with a Rust HTTP backend, a C++ native D3D/Metal engine, per-game engine routing, runtime bottles, installer profiles, and Linux `.deb` packaging. diff --git a/app/package.json b/app/package.json index b9ac53668..09698ab4b 100644 --- a/app/package.json +++ b/app/package.json @@ -136,6 +136,10 @@ "*.toml" ] }, + { + "from": "../scripts/install-metalsharp-wine-runtime.sh", + "to": "scripts/install-metalsharp-wine-runtime.sh" + }, { "from": "bundles/metalsharp-electron.tar.zst", "to": "bundles/metalsharp-electron.tar.zst" diff --git a/app/src-rust/src/installer.rs b/app/src-rust/src/installer.rs index aecfd305d..565b27811 100644 --- a/app/src-rust/src/installer.rs +++ b/app/src-rust/src/installer.rs @@ -31,6 +31,8 @@ const ASSETS_BUNDLE: &str = "metalsharp-assets"; const FNALIBS_BUNDLE: &str = "fnalibs"; const SCRIPTS_TOOLS_BUNDLE: &str = "metalsharp-scripts-tools"; const STEAM_BUNDLE: &str = "metalsharp-steam"; +const COMPLETE_RUNTIME_INSTALLER: &str = "install-metalsharp-wine-runtime.sh"; +const COMPLETE_RUNTIME_ARCHIVE_SHA256: &str = "93a456a40a7bf0ad2fecace5c01c58a366f85cc2901f6f8780c056c9e3b256ee"; const METALSHARP_NTDLL_HOOK_DLL: &str = "metalsharp_ntdll_hook.dll"; const DXMT_REQUIRED_PE: &[&str] = &[ "d3d10core.dll", @@ -319,21 +321,80 @@ fn install_steps() -> Vec { ("System Tools", Box::new(|_| install_xcode_cli())), ("Rosetta 2", Box::new(|_| install_rosetta())), ("Extract Tools (zstd)", Box::new(|_| ensure_zstd())), - ("Runtime Bundle Downloads", Box::new(ensure_runtime_bundle_assets)), - ("Runtime Assets", Box::new(install_metalsharp_bundle)), - ("Host Runtime ABI", Box::new(install_host_runtime)), - ("Support Assets", Box::new(install_split_assets_bundle)), - ("Scripts and Tools", Box::new(install_scripts_tools_bundle)), - ("DXMT Graphics Runtimes", Box::new(|home| ensure_graphics_runtimes_ready(home))), - ("Goldberg Steam Emulator", Box::new(install_goldberg)), - ("Steam Bridge Shim", Box::new(install_steam_bridge)), - ("Pipeline Rules", Box::new(install_mtsp_rules)), - ("Mono Configs", Box::new(install_mono_configs)), - ("Runtime Support", Box::new(|_| install_mono_arm64())), - ("FNA Shim Precompile", Box::new(|_| crate::mtsp::launcher::precompile_all_fna_shims().map(|_| true))), + ("Complete Multi-Architecture Runtime", Box::new(install_complete_runtime)), ] } +fn complete_runtime_root(home: &Path) -> PathBuf { + crate::platform::metalsharp_home_dir_for(home).join("runtime") +} + +fn complete_runtime_marker(root: &Path) -> PathBuf { + root.join(".metalsharp-runtime-install") +} + +pub fn complete_runtime_current_for_home(home: &Path) -> bool { + let root = complete_runtime_root(home); + let marker = match fs::read_to_string(complete_runtime_marker(&root)) { + Ok(marker) => marker, + Err(_) => return false, + }; + + marker.lines().any(|line| line == format!("archive_sha256={COMPLETE_RUNTIME_ARCHIVE_SHA256}")) + && file_nonempty(&root.join("wine/bin/metalsharp-wine")) + && file_nonempty(&root.join("wine/build-ec/wine")) + && file_nonempty(&root.join("wine/build-ec/server/wineserver")) + && file_nonempty(&root.join("wine/wine-11.12/nls/locale.nls")) + && file_nonempty(&root.join("wine/wine-11.12/fonts/tahoma.ttf")) + && file_nonempty(&root.join("graphics/dxvk/i386/d3d9.dll")) + && file_nonempty(&root.join("graphics/vkd3d-proton/x86_64/d3d12.dll")) + && file_nonempty(&root.join("graphics/opengl-metal/metalsharp-opengl.dylib")) + && file_nonempty(&root.join("graphics/moltenvk/libMoltenVK.dylib")) +} + +fn find_complete_runtime_installer() -> Option { + let mut candidates = Vec::new(); + if let Some(resources) = crate::platform::app_resources_dir() { + candidates.push(resources.join("scripts").join(COMPLETE_RUNTIME_INSTALLER)); + } + candidates + .push(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..").join("scripts").join(COMPLETE_RUNTIME_INSTALLER)); + candidates.push(PathBuf::from("scripts").join(COMPLETE_RUNTIME_INSTALLER)); + candidates.push(PathBuf::from("../scripts").join(COMPLETE_RUNTIME_INSTALLER)); + candidates.into_iter().find(|path| file_nonempty(path)) +} + +fn install_complete_runtime(home: &PathBuf) -> Result { + if complete_runtime_current_for_home(home) { + return Ok(false); + } + + let installer = find_complete_runtime_installer().ok_or_else(|| { + format!("{} is missing from the MetalSharp application resources", COMPLETE_RUNTIME_INSTALLER) + })?; + let target = complete_runtime_root(home); + let output = Command::new("/bin/bash") + .arg(&installer) + .args(["--target"]) + .arg(&target) + .args(["--replace", "--discard-backup"]) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .output() + .map_err(|e| format!("start complete runtime installer: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let detail = stderr.lines().rev().find(|line| !line.trim().is_empty()).unwrap_or("unknown installer error"); + return Err(format!("complete runtime installer failed: {detail}")); + } + if !complete_runtime_current_for_home(home) { + return Err("complete runtime installer exited successfully, but the runtime verification gate failed".into()); + } + + Ok(true) +} + fn runtime_bundle_assets_for_host() -> &'static [&'static str] { MAC_RUNTIME_BUNDLE_ASSETS } @@ -2540,21 +2601,6 @@ mod tests { let _ = fs::remove_dir_all(home); } - #[test] - fn runtime_bundle_preflight_knows_beta7_assets() { - let mac_assets = MAC_RUNTIME_BUNDLE_ASSETS; - for expected in [ - "metalsharp-runtime.tar.zst", - "metalsharp-graphics-dll.tar.zst", - "metalsharp-assets.tar.zst", - "fnalibs.tar.zst", - "metalsharp-scripts-tools.tar.zst", - "metalsharp-steam.tar.zst", - ] { - assert!(mac_assets.contains(&expected), "missing mac bundle asset {}", expected); - } - } - #[test] fn install_order_runs_xcode_cli_before_rosetta() { let names: Vec<&str> = install_steps().into_iter().map(|(name, _)| name).collect(); @@ -2566,18 +2612,46 @@ mod tests { } #[test] - fn install_steps_use_split_graphics_runtime_and_do_not_install_eac_toggle_or_gptk() { + fn install_steps_use_only_prerequisites_and_complete_runtime() { let names: Vec<&str> = install_steps().into_iter().map(|(name, _)| name).collect(); - assert!(names.contains(&"DXMT Graphics Runtimes")); - assert!(!names.contains(&"Offline EAC Mode")); - assert!( - names.iter().all(|name| !name.to_ascii_lowercase().contains("gptk")), - "first-time setup must not install GPTK; D3DMetal bottles own Homebrew GPTK setup: {:?}", - names + assert_eq!( + names, + vec!["System Tools", "Rosetta 2", "Extract Tools (zstd)", "Complete Multi-Architecture Runtime"] ); } + #[test] + fn complete_runtime_gate_requires_matching_marker_and_all_surfaces() { + let home = test_home("complete-runtime-gate"); + let root = complete_runtime_root(&home); + for rel in [ + "wine/bin/metalsharp-wine", + "wine/build-ec/wine", + "wine/build-ec/server/wineserver", + "wine/wine-11.12/nls/locale.nls", + "wine/wine-11.12/fonts/tahoma.ttf", + "graphics/dxvk/i386/d3d9.dll", + "graphics/vkd3d-proton/x86_64/d3d12.dll", + "graphics/opengl-metal/metalsharp-opengl.dylib", + "graphics/moltenvk/libMoltenVK.dylib", + ] { + let path = root.join(rel); + fs::create_dir_all(path.parent().expect("fixture parent")).expect("create fixture parent"); + fs::write(path, b"fixture").expect("write fixture"); + } + fs::write( + complete_runtime_marker(&root), + format!("archive_sha256={COMPLETE_RUNTIME_ARCHIVE_SHA256}\nno_tso=1\n"), + ) + .expect("write marker"); + + assert!(complete_runtime_current_for_home(&home)); + fs::remove_file(root.join("graphics/vkd3d-proton/x86_64/d3d12.dll")).expect("remove gate fixture"); + assert!(!complete_runtime_current_for_home(&home)); + let _ = fs::remove_dir_all(home); + } + #[test] fn graphics_bundle_layout_matches_release_manifest() { let manifest = include_str!("../../../tools/bundles/asset-manifest.tsv"); diff --git a/app/src-rust/src/setup.rs b/app/src-rust/src/setup.rs index 751940060..1cf2b5218 100644 --- a/app/src-rust/src/setup.rs +++ b/app/src-rust/src/setup.rs @@ -23,12 +23,13 @@ const DEFAULT_AGILITY_PACKAGE_VERSION: &str = "1.619.3"; pub fn state() -> Value { let home = dirs::home_dir().unwrap_or_default(); let config_path = crate::platform::metalsharp_home_dir_for(&home).join("setup.json"); + let complete_runtime_current = crate::installer::complete_runtime_current_for_home(&home); let dxmt_runtime = crate::installer::dxmt_runtime_status(); let dxmt_current = dxmt_runtime.get("current").and_then(|v| v.as_bool()).unwrap_or(false); let dxmt_m12_current = dxmt_runtime.get("m12Current").and_then(|v| v.as_bool()).unwrap_or(false); let wine_dir = crate::platform::metalsharp_home_dir_for(&home).join("runtime").join("wine"); let metalsharp_runtime_lib_ready = crate::installer::metalsharp_runtime_lib_ready(&wine_dir); - let runtime_current = dxmt_current && dxmt_m12_current && metalsharp_runtime_lib_ready; + let runtime_current = complete_runtime_current; if config_path.exists() { if let Ok(contents) = std::fs::read_to_string(&config_path) { @@ -42,6 +43,7 @@ pub fn state() -> Value { "deviceName": cfg.get("deviceName").and_then(|v| v.as_str()).unwrap_or(""), "steamApiKeySet": cfg.get("steamApiKeySet").and_then(|v| v.as_bool()).unwrap_or(false), "runtimeMigrationRequired": saved_completed && !runtime_current, + "completeRuntimeCurrent": complete_runtime_current, "dxmtRuntime": dxmt_runtime, "metalsharpRuntimeLibReady": metalsharp_runtime_lib_ready, }); @@ -57,6 +59,7 @@ pub fn state() -> Value { "deviceName": "", "steamApiKeySet": false, "runtimeMigrationRequired": false, + "completeRuntimeCurrent": complete_runtime_current, "dxmtRuntime": dxmt_runtime, "metalsharpRuntimeLibReady": metalsharp_runtime_lib_ready, }) @@ -127,8 +130,7 @@ pub fn dependencies() -> Value { || check_path(&PathBuf::from("/Applications/Steam.app/Contents/MacOS/steam_osx")); let homebrew = check_command("brew"); let moltenvk = check_path(&PathBuf::from("/opt/homebrew/etc/vulkan/icd.d/MoltenVK_icd.json")); - let metalsharp_wine = check_path(&crate::platform::metalsharp_home_dir_for(&home).join("runtime/wine/bin/wine")) - || check_path(&crate::platform::metalsharp_home_dir_for(&home).join("runtime/wine/bin/metalsharp-wine")); + let metalsharp_wine = crate::installer::complete_runtime_current_for_home(&home); let host_runtime = host_runtime_installed(&home); let dxmt_status = crate::installer::dxmt_runtime_status(); let dxmt_runtime = dxmt_status @@ -144,8 +146,7 @@ pub fn dependencies() -> Value { .and_then(|v| v.as_bool()) .unwrap_or(false); - let all_ok = - homebrew && rosetta && xcode_cli && metalsharp_wine && host_runtime && dxmt_runtime && dxmt_m12_runtime; + let all_ok = homebrew && rosetta && xcode_cli && metalsharp_wine; json!({ "ok": true, @@ -178,8 +179,8 @@ pub fn dependencies() -> Value { }, { "id": "metalsharp_wine", - "name": "MetalSharp Wine", - "desc": "From-source Wine 11.5 with DXMT Metal D3D11, gnutls TLS, MoltenVK. Runs Windows Steam and launches games with native Metal rendering.", + "name": "MetalSharp Complete Wine Runtime", + "desc": "Verified Wine 11.12 multi-architecture runtime with ARM64, ARM64EC, x86_64 and i386/WoW64 execution plus the complete Metal and Vulkan graphics stack.", "installed": metalsharp_wine, "required": true, "installCmd": "metalsharp-setup-wine", diff --git a/app/src/renderer/components/SetupWizard.vue b/app/src/renderer/components/SetupWizard.vue index f14a7e73a..423d39f65 100644 --- a/app/src/renderer/components/SetupWizard.vue +++ b/app/src/renderer/components/SetupWizard.vue @@ -307,7 +307,7 @@ async function installVcppX86() {

Install Runtime

-

Install the MetalSharp-owned Wine runtime, DXMT graphics runtimes, Steam support files, Mono/FNA support, scripts, and bottle rules. GPTK is not installed during first-time setup.

+

Download, verify, and install the complete MetalSharp Wine 11.12 runtime for ARM64, ARM64EC, x86_64, and i386/WoW64. The installer includes the Metal/Vulkan graphics stacks, fonts, TLS, media, Java, Mono, SDL, and runtime providers. GPTK remains a separate Homebrew-owned route.