From 0b19f11aa75ea1b6b1bb6046171dfa80b8284661 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:28:44 +0700 Subject: [PATCH] feat: Allow switching between multiple DX backends --- Harbor/Systems/BottleDX.swift | 225 ++++++++++++++++++ Harbor/Systems/BottleDXVK.swift | 96 -------- .../{DXVKUtils.swift => DXUtils.swift} | 10 +- Harbor/Systems/GPKUtils.swift | 46 +++- .../BottleManagement/BottleCardListView.swift | 60 +++-- .../MenuCommands/DXVKInstallView.swift | 6 +- 6 files changed, 323 insertions(+), 120 deletions(-) create mode 100644 Harbor/Systems/BottleDX.swift delete mode 100644 Harbor/Systems/BottleDXVK.swift rename Harbor/Systems/{DXVKUtils.swift => DXUtils.swift} (80%) diff --git a/Harbor/Systems/BottleDX.swift b/Harbor/Systems/BottleDX.swift new file mode 100644 index 0000000..6b1602e --- /dev/null +++ b/Harbor/Systems/BottleDX.swift @@ -0,0 +1,225 @@ +// +// MVKUtils.swift +// Harbor +// +// Created by Venti on 19/06/2023. +// + +import Foundation + +enum DXBackend: String { + case how = "did we get here?" + case gptk = "GPTK" + case dxvk = "DXVK" + case wined3d = "WineD3D" +} + +class BottleDX { + static let shared = BottleDX() + + let dxvkOverrides = ["dxgi", "d3d9", "d3d10core", "d3d11"] + let wined3dOverrides = ["dxgi", "d3d9", "d3d10", "d3d11", "d3d12"] + + func checkBottleBackend(for bottle: HarborBottle) -> DXBackend { + let bottlePath = bottle.path + // Check the backend.hrb file in the bottle's system32 + // If it doesn't exist -> GPTK + // If it does exist -> check the contents (DXVK, WineD3D) + let backendPath = bottlePath.appendingPathComponent("drive_c/windows/system32/backend.hrb") + if FileManager.default.fileExists(atPath: backendPath.path) { + do { + let backendContents = try String(contentsOf: backendPath) + if backendContents.contains("DXVK") { + return .dxvk + } else if backendContents.contains("WineD3D") { + return .wined3d + } + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + return .gptk + } + + func updateDXBackend(for bottle: HarborBottle, using backend: DXBackend) { + let bottlePath = bottle.path + let backendKind = checkBottleBackend(for: bottle) + if backendKind != .gptk { + revertToGPTK(for: bottle) + } + switch backend { + case .dxvk: + installDXVK(for: bottle) + case .wined3d: + installWineD3D(for: bottle) + default: + return + } + // Create the backend.hrb file + let backendPath = bottlePath.appendingPathComponent("drive_c/windows/system32/backend.hrb") + do { + try backend.rawValue.write(to: backendPath, atomically: true, encoding: .utf8) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + + func revertToGPTK(for bottle: HarborBottle) { + let bottlePath = bottle.path + let backendKind = checkBottleBackend(for: bottle) + if backendKind == .gptk { + return + } + switch backendKind { + case .dxvk: + removeDXVKFromBottle(bottle: bottle) + case .wined3d: + removeWineD3D(for: bottle) + default: + return + } + // Remove the backend.hrb file + let backendPath = bottlePath.appendingPathComponent("drive_c/windows/system32/backend.hrb") + if FileManager.default.fileExists(atPath: backendPath.path) { + do { + try FileManager.default.removeItem(at: backendPath) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + } + + func installDXVK(for bottle: HarborBottle) { + if !DXUtils.shared.isDXVKAvailable() { + return + } + + let bottlePath = bottle.path + let dxvkDirPath = HarborUtils.shared.getContainerHome().appendingPathComponent("dxvk") + + for override in dxvkOverrides { + let overridePath = bottlePath.appendingPathComponent("drive_c/windows/system32/\(override).dll") + // rename the original dll to .orig + if FileManager.default.fileExists(atPath: overridePath.path) { + do { + try FileManager.default.moveItem(at: overridePath, to: overridePath.appendingPathExtension("orig")) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + // symlink the dxvk dll + let dxvkOverridePath = dxvkDirPath.appendingPathComponent("\(override).dll") + do { + try FileManager.default.createSymbolicLink(at: overridePath, withDestinationURL: dxvkOverridePath) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + + // Add override to wine registry + for override in dxvkOverrides { + applyRegistryOverrides(in: bottle, for: override) + } + } + + func removeDXVKFromBottle(bottle: HarborBottle) { + let bottlePath = bottle.path + + for override in dxvkOverrides { + let overridePath = bottlePath.appendingPathComponent("drive_c/windows/system32/\(override).dll") + // remove the symlink + if FileManager.default.fileExists(atPath: overridePath.path) { + do { + try FileManager.default.removeItem(at: overridePath) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + // rename the original dll back + let overrideOrigPath = overridePath.appendingPathExtension("orig") + if FileManager.default.fileExists(atPath: overrideOrigPath.path) { + do { + try FileManager.default.moveItem(at: overrideOrigPath, to: overridePath) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + } + + // Remove override from wine registry + for override in dxvkOverrides { + removeRegistryOverrides(in: bottle, for: override) + } + } + + func installWineD3D(for bottle: HarborBottle) { + let bottlePath = bottle.path + let wined3dDirPath = HarborUtils.shared.getContainerHome().appendingPathComponent("wined3d") + + for override in wined3dOverrides { + let overridePath = bottlePath.appendingPathComponent("drive_c/windows/system32/\(override).dll") + // rename the original dll to .orig + if FileManager.default.fileExists(atPath: overridePath.path) { + do { + try FileManager.default.moveItem(at: overridePath, to: overridePath.appendingPathExtension("orig")) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + // symlink the wined3d dll + let wined3dOverridePath = wined3dDirPath.appendingPathComponent("\(override).dll") + do { + try FileManager.default.createSymbolicLink(at: overridePath, withDestinationURL: wined3dOverridePath) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + + // Add override to wine registry + for override in wined3dOverrides { + applyRegistryOverrides(in: bottle, for: override) + } + } + + func removeWineD3D(for bottle: HarborBottle) { + let bottlePath = bottle.path + + for override in wined3dOverrides { + let overridePath = bottlePath.appendingPathComponent("drive_c/windows/system32/\(override).dll") + // remove the symlink + if FileManager.default.fileExists(atPath: overridePath.path) { + do { + try FileManager.default.removeItem(at: overridePath) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + // rename the original dll back + let overrideOrigPath = overridePath.appendingPathExtension("orig") + if FileManager.default.fileExists(atPath: overrideOrigPath.path) { + do { + try FileManager.default.moveItem(at: overrideOrigPath, to: overridePath) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + } + } + } + + // Remove override from wine registry + for override in wined3dOverrides { + removeRegistryOverrides(in: bottle, for: override) + } + } + + func applyRegistryOverrides(in bottle: HarborBottle, for dll: String) { + bottle.directLaunchApplication("reg.exe", arguments: ["add", + #"HKEY_CURRENT_USER\Software\Wine\DllOverrides"#, "/v", + dll, "/d", "native", "/f"], shouldWait: true) + } + + func removeRegistryOverrides(in bottle: HarborBottle, for dll: String) { + bottle.directLaunchApplication("reg.exe", arguments: ["delete", + #"HKEY_CURRENT_USER\Software\Wine\DllOverrides"#, "/v", + dll, "/f"], shouldWait: true) + } +} diff --git a/Harbor/Systems/BottleDXVK.swift b/Harbor/Systems/BottleDXVK.swift deleted file mode 100644 index ecbf9c5..0000000 --- a/Harbor/Systems/BottleDXVK.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// MVKUtils.swift -// Harbor -// -// Created by Venti on 19/06/2023. -// - -import Foundation - -class BottleDXVK { - static let shared = BottleDXVK() - - func checkBottleForDXVK(bottle: HarborBottle) -> Bool { - let bottlePath = bottle.path - let dxvkUniqueOverrides = ["dxgi", "d3d9", "d3d11"] - for override in dxvkUniqueOverrides { - let overridePath = bottlePath.appendingPathComponent("drive_c/windows/system32/\(override).dll.orig") - if !FileManager.default.fileExists(atPath: overridePath.path) { - return false - } - } - return true - } - - func installDXVKToBottle(bottle: HarborBottle) { - if !DXVKUtils.shared.isDXVKAvailable() || checkBottleForDXVK(bottle: bottle) { - return - } - let bottlePath = bottle.path - let dxvkDirPath = HarborUtils.shared.getContainerHome().appendingPathComponent("dxvk") - - let dxvkOverrides = ["dxgi", "d3d9", "d3d10core", "d3d11"] - for override in dxvkOverrides { - let overridePath = bottlePath.appendingPathComponent("drive_c/windows/system32/\(override).dll") - // rename the original dll to .orig - if FileManager.default.fileExists(atPath: overridePath.path) { - do { - try FileManager.default.moveItem(at: overridePath, to: overridePath.appendingPathExtension("orig")) - } catch { - HarborUtils.shared.quickError(error.localizedDescription) - } - } - // symlink the dxvk dll - let dxvkOverridePath = dxvkDirPath.appendingPathComponent("\(override).dll") - do { - try FileManager.default.createSymbolicLink(at: overridePath, withDestinationURL: dxvkOverridePath) - } catch { - HarborUtils.shared.quickError(error.localizedDescription) - } - } - - // Add override to wine registry - for override in dxvkOverrides { - bottle.directLaunchApplication("reg.exe", arguments: ["add", - #"HKEY_CURRENT_USER\Software\Wine\DllOverrides"#, "/v", - override, "/d", "native", "/f"], shouldWait: true) - } - } - - func removeDXVKFromBottle(bottle: HarborBottle) { - if !checkBottleForDXVK(bottle: bottle) { - return - } - - let bottlePath = bottle.path - - let dxvkOverrides = ["dxgi", "d3d9", "d3d10core", "d3d11"] - for override in dxvkOverrides { - let overridePath = bottlePath.appendingPathComponent("drive_c/windows/system32/\(override).dll") - // remove the symlink - if FileManager.default.fileExists(atPath: overridePath.path) { - do { - try FileManager.default.removeItem(at: overridePath) - } catch { - HarborUtils.shared.quickError(error.localizedDescription) - } - } - // rename the original dll back - let overrideOrigPath = overridePath.appendingPathExtension("orig") - if FileManager.default.fileExists(atPath: overrideOrigPath.path) { - do { - try FileManager.default.moveItem(at: overrideOrigPath, to: overridePath) - } catch { - HarborUtils.shared.quickError(error.localizedDescription) - } - } - } - - // Remove override from wine registry - for override in dxvkOverrides { - bottle.directLaunchApplication("reg.exe", arguments: ["delete", - #"HKEY_CURRENT_USER\Software\Wine\DllOverrides"#, "/v", - override, "/f"], shouldWait: true) - } - } -} diff --git a/Harbor/Systems/DXVKUtils.swift b/Harbor/Systems/DXUtils.swift similarity index 80% rename from Harbor/Systems/DXVKUtils.swift rename to Harbor/Systems/DXUtils.swift index 87865db..d31f0d9 100644 --- a/Harbor/Systems/DXVKUtils.swift +++ b/Harbor/Systems/DXUtils.swift @@ -8,8 +8,8 @@ import Foundation import Observation -struct DXVKUtils { - static let shared = DXVKUtils() +struct DXUtils { + static let shared = DXUtils() var DXVKLibsAvailable: Bool { let harborContainer = HarborUtils.shared.getContainerHome() @@ -49,4 +49,10 @@ struct DXVKUtils { } task.waitUntilExit() } + + func isWineD3DAvailable() -> Bool { // Unfortunately have to exist because previously we didn't save it + let harborContainer = HarborUtils.shared.getContainerHome() + let wined3dDirPath = harborContainer.appendingPathComponent("wined3d").appendingPathComponent("d3d11.dll") + return FileManager.default.fileExists(atPath: wined3dDirPath.path) + } } diff --git a/Harbor/Systems/GPKUtils.swift b/Harbor/Systems/GPKUtils.swift index c6fa7d0..dfa133b 100644 --- a/Harbor/Systems/GPKUtils.swift +++ b/Harbor/Systems/GPKUtils.swift @@ -85,6 +85,9 @@ final class GPKUtils { checkGPKInstallStatus() } while self.status == .notInstalled + // Backup the original WineD3D libraries + saveWineD3Dlibs() + // Copy the GPK libraries mountAndCopyGPKLibs() } @@ -138,6 +141,9 @@ final class GPKUtils { checkGPKInstallStatus() } while self.status == .notInstalled + // Backup the original WineD3D libraries + saveWineD3Dlibs() + // Copy the GPK libraries if bundledGPK { copyGPKFromArchive(from: gpkBottle) @@ -269,7 +275,45 @@ final class GPKUtils { } } // Copy the GPK libraries - copyGPKLibraries() + mountAndCopyGPKLibs() + } + + func saveWineD3Dlibs() { + // Save the original WineD3D libraries + // d3d9.dll, d3d10.dll, d3d11.dll, d3d12.dll, dxgi.dll + let harborContainer = HarborUtils.shared.getContainerHome().appendingPathComponent("wined3d") + // Clean the folder if needed + + if FileManager.default.fileExists(atPath: harborContainer.path) { + do { + try FileManager.default.removeItem(at: harborContainer) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + return + } + } + do { + try FileManager.default.createDirectory(at: harborContainer, + withIntermediateDirectories: true, attributes: nil) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + return + } + + let gpkLib = URL(fileURLWithPath: "/usr/local/opt/game-porting-toolkit/lib/wine/x86_64-windows") + let wineD3Dlibs = ["d3d9.dll", "d3d10.dll", "d3d11.dll", "d3d12.dll", "dxgi.dll"] + for lib in wineD3Dlibs { + let libPath = gpkLib.appendingPathComponent(lib) + let libDest = harborContainer.appendingPathComponent(lib) + if FileManager.default.fileExists(atPath: libPath.path) { + do { + try FileManager.default.copyItem(at: libPath, to: libDest) + } catch { + HarborUtils.shared.quickError(error.localizedDescription) + return + } + } + } } func completelyRemoveGPK() { diff --git a/Harbor/UIElements/BottleManagement/BottleCardListView.swift b/Harbor/UIElements/BottleManagement/BottleCardListView.swift index 0efedf7..8f07eb0 100644 --- a/Harbor/UIElements/BottleManagement/BottleCardListView.swift +++ b/Harbor/UIElements/BottleManagement/BottleCardListView.swift @@ -274,26 +274,50 @@ struct BottleCardDetailedView: View { struct DXVKToggle: View { @Binding var bottle: HarborBottle - @State var canSetDXVK = false - @State var bottleDXVKStatus = false + @State var canSetDX = false + @State var bottleDXBackend: DXBackend = .how var body: some View { Group { - if canSetDXVK { - Toggle("sheet.advConf.dxvkToggle", isOn: $bottleDXVKStatus) - .disabled(!DXVKUtils.shared.isDXVKAvailable() || !canSetDXVK) - .onChange(of: bottleDXVKStatus) { _, newValue in - canSetDXVK = false - Task.detached { - if newValue { - BottleDXVK.shared.installDXVKToBottle(bottle: bottle) - } else { - BottleDXVK.shared.removeDXVKFromBottle(bottle: bottle) - } - Task { @MainActor in - canSetDXVK = true - } + if canSetDX { + // Dropdown to select DX backend + // Picker("sheet.advConf.DXBackend", selection: $bottleDXBackend) { + // Text(DXBackend.gptk.rawValue).tag(DXBackend.gptk) + // Text(DXBackend.dxvk.rawValue).tag(DXBackend.dxvk) + // .disabled(!DXUtils.shared.isDXVKAvailable()) + // Text(DXBackend.wined3d.rawValue).tag(DXBackend.wined3d) + // .disabled(!DXUtils.shared.isWineD3DAvailable()) + // } + HStack { + Text("sheet.advConf.DXBackend") + Spacer() + Menu { + Button(DXBackend.gptk.rawValue) { + bottleDXBackend = .gptk + } + Button(DXBackend.dxvk.rawValue) { + bottleDXBackend = .dxvk + } + .disabled(!DXUtils.shared.isDXVKAvailable()) + Button(DXBackend.wined3d.rawValue) { + bottleDXBackend = .wined3d + } + .disabled(!DXUtils.shared.isWineD3DAvailable()) + } label: { + Text(bottleDXBackend.rawValue) + } + .disabled(!canSetDX) + // Limit the length (else it looks stupid) + .frame(maxWidth: 100) + } + .onChange(of: bottleDXBackend) { _, newValue in + canSetDX = false + Task.detached { + BottleDX.shared.updateDXBackend(for: bottle, using: newValue) + Task { @MainActor in + canSetDX = true } } + } } else { HStack { Text("sheet.advConf.dxvkToggle") @@ -306,9 +330,9 @@ struct DXVKToggle: View { } .onAppear { Task.detached { - bottleDXVKStatus = BottleDXVK.shared.checkBottleForDXVK(bottle: bottle) + bottleDXBackend = BottleDX.shared.checkBottleBackend(for: bottle) Task { @MainActor in - canSetDXVK = true + canSetDX = true } } } diff --git a/Harbor/UIElements/MenuCommands/DXVKInstallView.swift b/Harbor/UIElements/MenuCommands/DXVKInstallView.swift index 7e53a83..40c0836 100644 --- a/Harbor/UIElements/MenuCommands/DXVKInstallView.swift +++ b/Harbor/UIElements/MenuCommands/DXVKInstallView.swift @@ -38,7 +38,7 @@ struct DXVKInstallView: View { } } } - .disabled(!DXVKUtils.shared.vulkanAvailable) + .disabled(!DXUtils.shared.vulkanAvailable) if dxvkPath != nil { Text("sheet.dxvk.dxvkSelected") .foregroundColor(.green) @@ -56,7 +56,7 @@ struct DXVKInstallView: View { if let dxvkPath = dxvkPath { isWorking = true Task.detached { - DXVKUtils.shared.untarDXVKLibs(dxvkZip: dxvkPath) + DXUtils.shared.untarDXVKLibs(dxvkZip: dxvkPath) Task { @MainActor in isWorking = false isPresented = false @@ -64,7 +64,7 @@ struct DXVKInstallView: View { } } } - .disabled(dxvkPath == nil || !DXVKUtils.shared.vulkanAvailable) + .disabled(dxvkPath == nil || !DXUtils.shared.vulkanAvailable) .buttonStyle(.borderedProminent) } .disabled(isWorking)