diff --git a/internal/emu/afterboot.go b/internal/emu/afterboot.go index 234ee8c..23d8906 100644 --- a/internal/emu/afterboot.go +++ b/internal/emu/afterboot.go @@ -59,8 +59,8 @@ type bootSetup int const ( // setupAdopted: the instance was already running and nothing is owed: the guest Vulkan fix is only checked (the - // zygote restart of mounting it would end the game running there), and the IME channel is judged by the build - // alone (the emulator log may belong to another boot). + // zygote restart of mounting it would end the game running there). Windows can identify the IME channel by + // its build; other hosts cannot confirm it from an unverified old emulator log. setupAdopted bootSetup = iota // setupLaunched: this call launched qemu; nobody can have used the guest yet, so everything runs. setupLaunched @@ -139,6 +139,9 @@ func (m *Manager) afterBoot(ctx context.Context, inst *avd.Instance, mode bootSe func() { m.setupBalloon(ctx, i) }, func() { // 6) cpuinfo of the ARM translation layer: one entry per vCPU, or Unity picks the lowest quality + if !bridgeGuestSupported(inst.Config) { + return // native ARM64 guests have no x86-to-ARM translation layer + } if remote, ok := m.pushGuestScript(ctx, i, guest.CPUInfoBind); ok { res := m.shell(ctx, i, "su 0 sh "+remote, 0) m.Log.Infof("实例 %d ARM 转译 cpuinfo: %s 核", i, orText(strings.TrimSpace(res.Out), "?")) @@ -247,6 +250,10 @@ func affinityLockPath() string { // on the host, production's included, is only read. Returns the note for the log and the plan (its Spare sizes the // busy loops of setupIdlepoll). func (m *Manager) tuneHostScheduling(p host.Process, cores int) (string, hostPlan) { + if !root.CurrentPlatform().Windows() { + plan := planHost(nil, hostCPUs(nil), m.otherQemus(p, cores), cores) + return "使用系统默认调度", plan + } var notes []string if err := host.SetAboveNormalPriority(p); err != nil { notes = append(notes, fmt.Sprintf("设优先级失败(%v)", err)) diff --git a/internal/emu/createinit.go b/internal/emu/createinit.go index a7195e6..097e83c 100644 --- a/internal/emu/createinit.go +++ b/internal/emu/createinit.go @@ -469,7 +469,10 @@ func (m *Manager) checkCreated(ctx context.Context, i int, bridge bool, sr *Star missing = append(missing, "首次初始化") } if !sr.HostIME { - missing = append(missing, "宿主输入法") + channel, _ := m.hostImeChannel(true, sr.LogFile) + if root.CurrentPlatform().Windows() || channel { + missing = append(missing, "宿主输入法") + } } if bridge && m.guestBridgeWithin(ctx, i, statusAdbTimeout) != bridgeProp { missing = append(missing, fmt.Sprintf("arm64 执行桥(日志见 logs 里的 modavd-bridge-%d-*.log)", i)) diff --git a/internal/emu/gpuenv_darwin.go b/internal/emu/gpuenv_darwin.go new file mode 100644 index 0000000..f86b04d --- /dev/null +++ b/internal/emu/gpuenv_darwin.go @@ -0,0 +1,35 @@ +//go:build darwin + +package emu + +import ( + "os" + "path/filepath" + "time" +) + +const hideRebar = "0" + +// MoltenVK exposes Apple unified memory as device-local and host-visible. +// The PCIe ReBAR workaround would hide those guest-visible mapping flags. +func emulatorGPUEnv(string) []string { + return []string{"ANDROID_EMU_VK_HIDE_REBAR=" + hideRebar} +} + +// Reserve a log for this launch. The process backend appends, so a name based +// only on seconds could reuse another launch's HostIME registration message. +func emulatorLogPath(dir, name string) (string, error) { + if err := os.MkdirAll(dir, 0o755); err != nil { + return "", err + } + f, err := os.CreateTemp(dir, name+"-"+time.Now().Format("20060102-150405")+"-*.log") + if err != nil { + return "", err + } + path := filepath.Clean(f.Name()) + if err := f.Close(); err != nil { + os.Remove(path) + return "", err + } + return path, nil +} diff --git a/internal/emu/gpuenv_other.go b/internal/emu/gpuenv_other.go new file mode 100644 index 0000000..ffc2bea --- /dev/null +++ b/internal/emu/gpuenv_other.go @@ -0,0 +1,23 @@ +//go:build !darwin + +package emu + +import ( + "path/filepath" + "time" +) + +const hideRebar = "1" + +func emulatorGPUEnv(shaderCache string) []string { + return []string{ + "__GL_SHADER_DISK_CACHE=1", + "__GL_SHADER_DISK_CACHE_PATH=" + shaderCache, + "__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1", + "ANDROID_EMU_VK_HIDE_REBAR=" + hideRebar, + } +} + +func emulatorLogPath(dir, name string) (string, error) { + return filepath.Join(dir, name+"-"+time.Now().Format("20060102-150405")+".log"), nil +} diff --git a/internal/emu/ime.go b/internal/emu/ime.go index 8303676..bef6930 100644 --- a/internal/emu/ime.go +++ b/internal/emu/ime.go @@ -13,6 +13,7 @@ import ( "modavd/internal/adb" "modavd/internal/apk" + "modavd/internal/root" ) // The host input method org.modavd.ime: the guest half of the emulator's modavd_ime channel; what the user types @@ -195,17 +196,23 @@ func (p hostImePlan) commands() []string { return cmds } -// hostImeChannel: the emulator has the modavd_ime channel. AUTO-MAS checks the build (mas-35+); the launcher checks -// the emulator log of the boot it started. Either is enough (a dev SDK's build id may be anything). why says what +// hostImeChannel verifies the modavd_ime channel. Windows accepts the mas-35+ +// build contract or the current boot's registration log. Other hosts require +// that registration in the verified current emulator boot's log. why says what // is missing. func (m *Manager) hostImeChannel(launched bool, logPath string) (ok bool, why string) { build, known := m.Root.ModBuild() - if known && build >= hostImeMinBuild { + // The build-number contract describes the Windows core. Other hosts need + // the registration message from the boot being configured. + if root.CurrentPlatform().Windows() && known && build >= hostImeMinBuild { return true, "" } if launched && fileContains(logPath, hostImeChannelLine) { return true, "" } + if !root.CurrentPlatform().Windows() { + return false, "当前开机日志没有 modavd_ime 通道注册记录" + } if known { return false, fmt.Sprintf("内测包 mas-%d 低于 mas-%d", build, hostImeMinBuild) } diff --git a/internal/emu/launch.go b/internal/emu/launch.go index 11faecb..b4482a8 100644 --- a/internal/emu/launch.go +++ b/internal/emu/launch.go @@ -54,6 +54,11 @@ func buildLaunchArgs(avdName string, console, memoryMB int, headless bool) []str if headless { args = append(args, "-no-window", "-no-boot-anim") } + if p := root.CurrentPlatform(); p.MirrorOS == "macos" && p.GuestABI == "arm64-v8a" { + // API35 uses guest ANGLE over Vulkan/MoltenVK for GLES3.1 on Apple + // Silicon. -gpu host alone selects the older host GLES translation path. + args = append(args, "-feature", "GuestAngle,Vulkan") + } return append(args, "-feature", "-QuickbootFileBacked", "-feature", "-VirtioSndCard", @@ -148,8 +153,10 @@ func (m *Manager) launchPlanFor(ctx context.Context, inst *avd.Instance, memoryM headless := !window args := buildLaunchArgs(inst.Name, port, memoryMB, headless) m.pruneLogs(i) - stamp := time.Now().Format("20060102-150405") - logPath := filepath.Join(m.Root.LogsDir(), inst.Name+"-"+stamp+".log") + logPath, err := emulatorLogPath(m.Root.LogsDir(), inst.Name) + if err != nil { + return nil, errs.Wrap(errs.Internal, err, "创建模拟器日志失败: "+err.Error()) + } mode := "带窗口" if headless { @@ -159,8 +166,8 @@ func (m *Manager) launchPlanFor(ctx context.Context, inst *avd.Instance, memoryM if n, ok := inst.CPU(); ok { cpu = strconv.Itoa(n) } - m.Log.Infof("启动魔改 AVD 实例 %d(%s,%dp %d Hz,%d MB / %s 核,气球开,不带声卡,ANDROID_EMU_VK_HIDE_REBAR=1): emulator %s", - i, mode, inst.Resolution(), inst.FPS(), memoryMB, cpu, strings.Join(args, " ")) + m.Log.Infof("启动魔改 AVD 实例 %d(%s,%dp %d Hz,%d MB / %s 核,气球开,不带声卡,ANDROID_EMU_VK_HIDE_REBAR=%s): emulator %s", + i, mode, inst.Resolution(), inst.FPS(), memoryMB, cpu, hideRebar, strings.Join(args, " ")) return &launchPlan{args: args, logPath: logPath, memoryMB: memoryMB}, nil } @@ -265,7 +272,7 @@ func (m *Manager) startEmulator(i int, plan *launchPlan) (host.Process, error) { }) if err != nil { return host.Process{}, errs.Wrap(errs.LaunchFailed, err, - fmt.Sprintf("魔改 AVD 实例 %d 启动失败(emulator.exe 没能运行): %v", i, err)) + fmt.Sprintf("魔改 AVD 实例 %d 启动失败(%s 没能运行): %v", i, root.CurrentPlatform().Emulator, err)) } return p, nil } @@ -277,15 +284,11 @@ func (m *Manager) startEmulator(i int, plan *launchPlan) (host.Process, error) { // the guest as device local only. Endfield waited for write-combine drains on every vertex write there (title // screen 14.6 -> 60 fps); games that never touch that memory are unaffected, emulators before mas-27 ignore it. // -// Appended after the private adb environment: host.StartOrphan / StartDetached keep the last of entries with the same -// name (compared case-insensitively), so these override a user's own values, as AUTO-MAS env.update does. +// Darwin keeps unified memory host-visible and does not inject the NVIDIA cache +// settings. The backend keeps the last environment entry for each name, using +// the platform's case rules, so these override inherited values. func (m *Manager) emulatorEnv(shaderCache string) []string { - return append(m.ADB.Env(), - "__GL_SHADER_DISK_CACHE=1", - "__GL_SHADER_DISK_CACHE_PATH="+shaderCache, - "__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1", - "ANDROID_EMU_VK_HIDE_REBAR=1", - ) + return append(m.ADB.Env(), emulatorGPUEnv(shaderCache)...) } // logTail is the last n non-empty lines of the emulator log joined by " | " (AUTO-MAS _log_tail), for the error when diff --git a/internal/emu/vulkanfix.go b/internal/emu/vulkanfix.go index 2fb9e5e..9fb954f 100644 --- a/internal/emu/vulkanfix.go +++ b/internal/emu/vulkanfix.go @@ -11,6 +11,7 @@ import ( "time" "modavd/internal/adb" + "modavd/internal/avd" "modavd/internal/guest" "modavd/internal/host" "modavd/internal/root" @@ -22,13 +23,32 @@ const ( frameworkBackTimeout = 120 * time.Second ) +// guestVulkanProfile uses the instance's configured ABI, never the host OS. +// Do not guess when a config is unreadable or belongs to an unsupported guest. +func (m *Manager) guestVulkanProfile(i int) (vkfix.Profile, error) { + cfg, err := avd.ReadIni(avd.ConfigPath(m.Root, i)) + if err != nil { + return vkfix.Profile{}, fmt.Errorf("读取实例客体架构: %w", err) + } + abi := cfg.Value("abi.type") + profile, supported := vkfix.ForABI(abi) + if !supported { + if abi == "arm64-v8a" { + return vkfix.Profile{}, fmt.Errorf("ARM64 API35 Vulkan 补丁尚未验收,保留镜像原库;不套用 API34 补丁,也不挂载或重启 zygote") + } + return vkfix.Profile{}, fmt.Errorf("客体架构 %q 没有适用的 Vulkan 补丁", abi) + } + return profile, nil +} + // ensureVulkanFix bind-mounts the patched guest Vulkan encoder made on this host over /vendor/lib64/libvulkan_enc.so -// and restarts zygote (AUTO-MAS _ensure_vulkan_fix; launcher -VulkanFix), on every instance for every game (user +// and restarts zygote (AUTO-MAS _ensure_vulkan_fix; launcher -VulkanFix), for verified API34 x86_64 guests (user // 10-16). The patch (internal/vkfix) fixes Star Rail's descriptor templates (khrfix) and Endfield's unlocked -// vkBindImageMemory lookups (bindlock); games that use neither path are unaffected. +// vkBindImageMemory lookups (bindlock); games that use neither path are unaffected. ARM64 API35 keeps its original +// libraries until those separate Vulkan workloads have been validated; GLES3.1 capability does not prove either fix. // // - vulkanfix.sh first checks whether this boot already has it mounted (nothing to do then), then the image -// library's hash (anything but 898c0bbc... is left alone); the patched copy is cached in the guest's +// library's hash against the configured guest ABI's profile; the patched copy is cached in the guest's // /data/local/tmp, so the next boot mounts it without an upload. // - Without a guest cache the host copy in \guest-patches is pushed; without that, the guest's own library // is pulled and patched here (patchedVulkanLibrary). The patched file is never shipped. @@ -44,9 +64,14 @@ const ( // // Failures are only logged. Returns whether zygote was restarted, and whether the fix is still owed. func (m *Manager) ensureVulkanFix(ctx context.Context, i int, mode bootSetup, until time.Time) (restarted, pending bool) { + profile, err := m.guestVulkanProfile(i) + if err != nil { + m.Log.Warnf("实例 %d 客体 Vulkan 驱动补丁未执行: %v", i, err) + return false, false + } if mode == setupAdopted { res := m.shell(ctx, i, vulkanFixCheckScript, 0) - if warn, msg := vulkanFixCheckReport(i, res.Out); warn { + if warn, msg := vulkanFixCheckReport(i, res.Out, profile); warn { m.Log.Warnf("%s", msg) } else { m.Log.Infof("%s", msg) @@ -62,11 +87,11 @@ func (m *Manager) ensureVulkanFix(ctx context.Context, i int, mode bootSetup, un return m.mountVulkanFix(ctx, i, vulkanFixIO{ guestIO: g, remote: remote, - library: func() string { return m.patchedVulkanLibrary(ctx, i) }, + library: func() string { return m.patchedVulkanLibrary(ctx, i, profile) }, waitBack: func(uiBefore string) bool { return m.waitFrameworkBack(ctx, g, uiBefore, frameworkBackTimeout) }, - }, mode == setupOwed, until) + }, profile, mode == setupOwed, until) } // vulkanFixIO is what mountVulkanFix works through: the instance's adb in Manager, a fake guest in tests. @@ -132,10 +157,10 @@ func (m *Manager) owedMountGate(i int, g guestIO) (release func(), b mountBlocke // mountVulkanFix is the mounting part of ensureVulkanFix (a launched boot, or an owed one, owed=true: every run that // may mount passes owedMountGate first; until is the marker's deadline for the messages). Returns whether zygote was // restarted and whether an owed fix is still owed (deferred while the guest is in use). -func (m *Manager) mountVulkanFix(ctx context.Context, i int, fx vulkanFixIO, owed bool, until time.Time) (restarted, +func (m *Manager) mountVulkanFix(ctx context.Context, i int, fx vulkanFixIO, profile vkfix.Profile, owed bool, until time.Time) (restarted, pending bool) { runFix := func() string { - res := fx.shell("su 0 sh "+fx.remote+" "+vkfix.OrigSHA256+" "+vkfix.FixSHA256, vulkanFixScriptTimeout) + res := fx.shell("su 0 sh "+fx.remote+" "+profile.OrigSHA256+" "+profile.FixSHA256, vulkanFixScriptTimeout) return lastNonEmptyLine(res.Out) } // gatedFix is one vulkanfix.sh run, for an owed fix only once owedMountGate allows it, with the guest lock held @@ -167,7 +192,7 @@ func (m *Manager) mountVulkanFix(ctx context.Context, i int, fx vulkanFixIO, owe return "", false, true } res := fx.shell(vulkanFixCheckScript, 0) - pending, msg := vulkanFixDeferredReport(i, res.Out, b, until, time.Now()) + pending, msg := vulkanFixDeferredReport(i, res.Out, b, until, time.Now(), profile) if pending { m.Log.Warnf("%s", msg) } else { @@ -192,7 +217,7 @@ func (m *Manager) mountVulkanFix(ctx context.Context, i int, fx vulkanFixIO, owe m.Log.Warnf("实例 %d 客体 Vulkan 驱动补丁没挂: 推送补丁库失败: %s", i, tail(res.Out, 200)) return false, false } - m.Log.Infof("实例 %d 客体 Vulkan 驱动补丁 第 2 步: 已推送 %s(sha256 %s)到 %s", i, library, vkfix.FixSHA256, + m.Log.Infof("实例 %d 客体 Vulkan 驱动补丁 第 2 步: 已推送 %s(sha256 %s)到 %s", i, library, profile.FixSHA256, vkfix.UploadPath) if status, ok, pending = gatedFix(false); !ok { return false, pending @@ -203,7 +228,7 @@ func (m *Manager) mountVulkanFix(ctx context.Context, i int, fx vulkanFixIO, owe kind, sha := vulkanFixStatus(status) switch kind { case "ALREADY": - if sha == vkfix.FixSHA256 { + if sha == profile.FixSHA256 { m.Log.Infof("实例 %d 客体 Vulkan 驱动补丁本次开机已挂上(sha256 %s)", i, sha) } else { m.Log.Warnf("实例 %d 客体驱动库上已经挂着别的文件(sha256 %s),不是这次的补丁,没有动它", i, sha) @@ -243,14 +268,14 @@ const vulkanFixCheckScript = `if grep -q ' ` + vkfix.GuestPath + ` ' /proc/self/ // missing: the instance runs a whole boot without it then (Star Rail's descriptor templates, Endfield's // vkBindImageMemory), and the warning says how to get it. Otherwise this boot's state is only logged: an image whose // library already is the patched one, or an unexpected image (the start that booted it reported that already). -func vulkanFixCheckReport(i int, out string) (warn bool, msg string) { +func vulkanFixCheckReport(i int, out string, profile vkfix.Profile) (warn bool, msg string) { line := lastNonEmptyLine(out) switch sha, isImage := strings.CutPrefix(line, "image "); { case line == "mounted": return false, fmt.Sprintf("实例 %d 客体 Vulkan 驱动补丁: 本次开机已挂上", i) - case isImage && sha == vkfix.FixSHA256: + case isImage && sha == profile.FixSHA256: return false, fmt.Sprintf("实例 %d 镜像自带的客体驱动库已是补丁版,不用挂", i) - case isImage && sha == vkfix.OrigSHA256: + case isImage && sha == profile.OrigSHA256: return true, fmt.Sprintf("实例 %d 客体 Vulkan 驱动补丁这次开机没挂上:实例已在运行,挂它要重启 zygote(会结束正在运行的应用),"+ "这次不挂;要挂就先关掉这个实例再启动", i) case isImage && len(sha) == 64: @@ -367,13 +392,13 @@ func validPackageName(s string) bool { // guest is in use (b from owedMountGate). pending: the fix applies to this image and is missing, or that cannot be // read; the setup marker then stays and msg is vulkanFixDeferredMessage's. Otherwise nothing is owed (mounted // already, patched image, unexpected image) and msg is vulkanFixCheckReport's. -func vulkanFixDeferredReport(i int, out string, b mountBlocker, until, now time.Time) (pending bool, msg string) { - warn, checkMsg := vulkanFixCheckReport(i, out) +func vulkanFixDeferredReport(i int, out string, b mountBlocker, until, now time.Time, profile vkfix.Profile) (pending bool, msg string) { + warn, checkMsg := vulkanFixCheckReport(i, out, profile) if !warn { return false, checkMsg } detail := b.detail - if sha, isImage := strings.CutPrefix(lastNonEmptyLine(out), "image "); !isImage || sha != vkfix.OrigSHA256 { + if sha, isImage := strings.CutPrefix(lastNonEmptyLine(out), "image "); !isImage || sha != profile.OrigSHA256 { unknown := "这次开机挂没挂也读不出: " + orText(tail(out, 200), "无输出") if detail == "" { detail = unknown @@ -423,9 +448,9 @@ func vulkanFixStatus(status string) (kind, rest string) { // // Two instances (separate modavd processes) may build it at once: each writes its own temp file and renames it // over the cache; one that cannot rename takes the other's file when that has the right hash. -func (m *Manager) patchedVulkanLibrary(ctx context.Context, i int) string { +func (m *Manager) patchedVulkanLibrary(ctx context.Context, i int, profile vkfix.Profile) string { dir := m.Root.GuestPatchDir() - library := filepath.Join(dir, vkfix.CacheName) + library := filepath.Join(dir, profile.CacheName) ioFail := func(err error) string { m.Log.Warnf("实例 %d 客体 Vulkan 驱动补丁没挂: 本机读写补丁库失败(%s): %v", i, library, err) return "" @@ -433,7 +458,7 @@ func (m *Manager) patchedVulkanLibrary(ctx context.Context, i int) string { if data, err := os.ReadFile(library); err == nil { digest := vkfix.SHA256Hex(data) - if digest == vkfix.FixSHA256 { + if digest == profile.FixSHA256 { m.Log.Infof("实例 %d 客体 Vulkan 驱动补丁: 用本机缓存 %s", i, library) return library } @@ -458,19 +483,19 @@ func (m *Manager) patchedVulkanLibrary(ctx context.Context, i int) string { return ioFail(err) } m.Log.Infof("实例 %d 从客体拉到驱动原库(sha256 %s),本机生成补丁库", i, vkfix.SHA256Hex(data)) - patched, err := vkfix.Patch(data) + patched, err := profile.Patch(data) if err != nil { m.Log.Warnf("实例 %d 客体 Vulkan 驱动补丁没挂: %v", i, err) return "" } if err := writeFileReplace(library, patched, fmt.Sprintf("%d-%d", os.Getpid(), i)); err != nil { - if b, rerr := os.ReadFile(library); rerr == nil && vkfix.SHA256Hex(b) == vkfix.FixSHA256 { + if b, rerr := os.ReadFile(library); rerr == nil && vkfix.SHA256Hex(b) == profile.FixSHA256 { // another start wrote the same file meanwhile (and may be pushing it right now) return library } return ioFail(err) } - m.Log.Infof("实例 %d 已在本机生成客体驱动补丁库(sha256 %s): %s", i, vkfix.FixSHA256, library) + m.Log.Infof("实例 %d 已在本机生成客体驱动补丁库(sha256 %s): %s", i, profile.FixSHA256, library) return library } diff --git a/internal/emu/vulkanfix_test.go b/internal/emu/vulkanfix_test.go index b3c046e..b2f72c1 100644 --- a/internal/emu/vulkanfix_test.go +++ b/internal/emu/vulkanfix_test.go @@ -66,7 +66,7 @@ func TestVulkanFixCheckReport(t *testing.T) { {"", true, "无输出"}, } for _, c := range cases { - warn, msg := vulkanFixCheckReport(4, c.out) + warn, msg := vulkanFixCheckReport(4, c.out, vkfix.X86Profile()) if warn != c.warn || !strings.HasPrefix(msg, "实例 4 ") || !strings.Contains(msg, c.has) { t.Errorf("vulkanFixCheckReport(%q) = %v %q", c.out, warn, msg) } @@ -202,7 +202,7 @@ func TestVulkanFixDeferredReport(t *testing.T) { {"unexpected image", "image " + strings.Repeat("cd", 32) + "\n", foregroundBlocker(""), until, false, "不是预期的镜像版本"}, } for _, c := range cases { - pending, msg := vulkanFixDeferredReport(3, c.out, c.b, c.until, now) + pending, msg := vulkanFixDeferredReport(3, c.out, c.b, c.until, now, vkfix.X86Profile()) okMsg := msg == c.msg if !strings.HasPrefix(c.msg, "实例") { okMsg = strings.HasPrefix(msg, "实例 3 ") && strings.Contains(msg, c.msg) @@ -376,7 +376,7 @@ func TestMountVulkanFix(t *testing.T) { f.onPush = takeInstall } - restarted, pending := m.mountVulkanFix(context.Background(), 3, f.io(), c.owed, until) + restarted, pending := m.mountVulkanFix(context.Background(), 3, f.io(), vkfix.X86Profile(), c.owed, until) install.Unlock() if restarted != c.restarted || pending != c.pending { diff --git a/internal/vkfix/profile.go b/internal/vkfix/profile.go new file mode 100644 index 0000000..ed3aef9 --- /dev/null +++ b/internal/vkfix/profile.go @@ -0,0 +1,27 @@ +package vkfix + +// Profile keeps a verified guest library's byte contract together. An ABI alone +// does not make a different Android release compatible with a binary patch. +type Profile struct { + OrigSHA256 string + FixSHA256 string + CacheName string + Patch func([]byte) ([]byte, error) +} + +func X86Profile() Profile { + return Profile{OrigSHA256: OrigSHA256, FixSHA256: FixSHA256, CacheName: CacheName, Patch: Patch} +} + +// ForABI selects a patch contract for the instance's guest ABI, independently +// of the manager's host architecture. Only the API34 x86_64 patch is verified. +// The Mac API35 image has no /vendor/lib64/libvulkan_enc.so; its different Vulkan +// HAL must not inherit either the retired API34 ARM64 edits or the x86 patch. +func ForABI(abi string) (Profile, bool) { + switch abi { + case "x86_64": + return X86Profile(), true + default: + return Profile{}, false + } +}