Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a070a41
feat(macos): define ARM64 guest profile and emulator paths
DeathofBrain Nov 5, 2026
55f5530
chore: remove test changes from macOS profile PR
DeathofBrain Nov 5, 2026
47e52c2
Merge upstream main and retain 1080p resolution defaults
DeathofBrain Nov 5, 2026
5ad74e7
feat: implement Darwin process runtime primitives
DeathofBrain Nov 5, 2026
1432755
Merge remote-tracking branch 'origin/main' into codex/darwin-process-…
DeathofBrain Nov 5, 2026
fcefa74
refactor: separate host routing from platform backends
DeathofBrain Nov 5, 2026
41fa471
Merge main into codex/darwin-process-runtime and resolve host conflicts
DeathofBrain Nov 5, 2026
a24cd4c
Merge latest main and retain portable host test imports
DeathofBrain Nov 5, 2026
f829e44
Merge remote-tracking branch 'origin/main' into codex/macos-arm64-pro…
DeathofBrain Nov 5, 2026
acd96b5
test: sync existing platform expectations for ARM64 profiles
DeathofBrain Nov 5, 2026
36197b7
Merge remote-tracking branch 'origin/main' into codex/macos-arm64-pro…
DeathofBrain Nov 5, 2026
e1ddfd2
Merge latest main and retain host routing for compact and logcat
DeathofBrain Nov 5, 2026
81ef060
Merge implemented macOS stages 1 and 2
DeathofBrain Nov 5, 2026
3cca735
feat: configure native ARM64 guests and Vulkan patch profiles
DeathofBrain Nov 5, 2026
d22e58a
Merge remote-tracking branch 'origin/main' into codex/macos-1-api35-r…
DeathofBrain Nov 6, 2026
22e3dbf
fix: select Android API level from the host platform table
DeathofBrain Nov 6, 2026
71bb88c
Merge current main and preserve host routing for new upstream calls
DeathofBrain Nov 6, 2026
489f193
Merge branch 'codex/macos-1-api35-review' into codex/macos-review-base
DeathofBrain Nov 6, 2026
cdb2ebd
Merge branch 'codex/macos-review-base' into codex/macos-5-review
DeathofBrain Nov 6, 2026
365d71c
fix: retire API34 ARM64 Vulkan patch for macOS API35
DeathofBrain Nov 6, 2026
4531acb
fix: use named macOS system image constant
DeathofBrain Nov 6, 2026
8c61f79
Merge branch 'codex/macos-1-api35-review' into codex/macos-review-base
DeathofBrain Nov 6, 2026
53ffbab
Merge branch 'codex/macos-review-base' into codex/macos-5-review
DeathofBrain Nov 6, 2026
1bbd961
Merge current main after macOS platform and host integration
DeathofBrain Nov 6, 2026
50966f9
fix: remove retired ARM64 guest patch cache path
DeathofBrain Nov 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions internal/emu/afterboot.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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), "?"))
Expand Down Expand Up @@ -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))
Expand Down
5 changes: 4 additions & 1 deletion internal/emu/createinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
35 changes: 35 additions & 0 deletions internal/emu/gpuenv_darwin.go
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions internal/emu/gpuenv_other.go
Original file line number Diff line number Diff line change
@@ -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
}
13 changes: 10 additions & 3 deletions internal/emu/ime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
29 changes: 16 additions & 13 deletions internal/emu/launch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down
71 changes: 48 additions & 23 deletions internal/emu/vulkanfix.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"modavd/internal/adb"
"modavd/internal/avd"
"modavd/internal/guest"
"modavd/internal/host"
"modavd/internal/root"
Expand All @@ -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 <root>\guest-patches is pushed; without that, the guest's own library
// is pulled and patched here (patchedVulkanLibrary). The patched file is never shipped.
Expand All @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -423,17 +448,17 @@ 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 ""
}

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
}
Expand All @@ -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
}

Expand Down
Loading
Loading