From 4115d2a2585d401123bfc45bc40351b000411471 Mon Sep 17 00:00:00 2001 From: esiayo <41133734+vapidinfinity@users.noreply.github.com> Date: Wed, 15 Jan 2025 02:28:24 +0800 Subject: [PATCH] feat!: dxvk support --- Mythic.xcodeproj/project.pbxproj | 8 +- Mythic/Localizable.xcstrings | 15 +++ .../Extensions/Built-in/FileManager.swift | 16 +++ .../Legendary/LegendaryInterface.swift | 12 +- Mythic/Utilities/Local/LocalGames.swift | 25 +++-- Mythic/Utilities/Wine/WineInterface.swift | 6 +- .../Sheets/ContainerSettingsView.swift | 104 +++++++++++++++++- 7 files changed, 168 insertions(+), 18 deletions(-) create mode 100644 Mythic/Utilities/Extensions/Built-in/FileManager.swift diff --git a/Mythic.xcodeproj/project.pbxproj b/Mythic.xcodeproj/project.pbxproj index 5a31671..3a88aba 100644 --- a/Mythic.xcodeproj/project.pbxproj +++ b/Mythic.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 5A62AE982C27DB1200BA31D2 /* GameListEvoVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A62AE972C27DB1200BA31D2 /* GameListEvoVM.swift */; }; 5A9573AA2C29BBEC009C8F85 /* SparkleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9573A92C29BBEC009C8F85 /* SparkleController.swift */; }; 6A0688442C2BCE8B004DF10F /* DownloadCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0688432C2BCE87004DF10F /* DownloadCard.swift */; }; + 6A08A29B2D36E0B800E6AEFB /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A08A29A2D36E0B500E6AEFB /* FileManager.swift */; }; 6A12FF8E2B73AC4E00AA948C /* Glur in Frameworks */ = {isa = PBXBuildFile; productRef = 6A12FF8D2B73AC4E00AA948C /* Glur */; }; 6A1357B32CE4B0CD00B19213 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1357B22CE4B0C900B19213 /* SemanticVersion.swift */; }; 6A2935322BFCFAFD0035CE4B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2934AE2BFCFAFD0035CE4B /* Preview Assets.xcassets */; }; @@ -113,6 +114,7 @@ 5A62AE972C27DB1200BA31D2 /* GameListEvoVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameListEvoVM.swift; sourceTree = ""; }; 5A9573A92C29BBEC009C8F85 /* SparkleController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkleController.swift; sourceTree = ""; }; 6A0688432C2BCE87004DF10F /* DownloadCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadCard.swift; sourceTree = ""; }; + 6A08A29A2D36E0B500E6AEFB /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; 6A1357B22CE4B0C900B19213 /* SemanticVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemanticVersion.swift; sourceTree = ""; }; 6A2934AE2BFCFAFD0035CE4B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 6A2934B02BFCFAFD0035CE4B /* Engine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Engine.swift; sourceTree = ""; }; @@ -424,6 +426,7 @@ 6AD44DE52C0A29BF00824C06 /* Built-in */ = { isa = PBXGroup; children = ( + 6A08A29A2D36E0B500E6AEFB /* FileManager.swift */, 6ACFAD8A2D356910009B1554 /* Bool.swift */, 6A1357B22CE4B0C900B19213 /* SemanticVersion.swift */, 6A2960FF2CE1033000917E90 /* NSWindow.swift */, @@ -600,6 +603,7 @@ 6A2935612BFCFAFD0035CE4B /* GameListViewEvo.swift in Sources */, 6A29354F2BFCFAFD0035CE4B /* DownloadsViewEvo.swift in Sources */, 6A496A732C1AF75B00FD637B /* Game.swift in Sources */, + 6A08A29B2D36E0B800E6AEFB /* FileManager.swift in Sources */, 6A2935602BFCFAFD0035CE4B /* ContainerListView.swift in Sources */, 6A29355E2BFCFAFD0035CE4B /* StopDownloadAlert.swift in Sources */, 6A2935662BFCFAFD0035CE4B /* AppDelegate.swift in Sources */, @@ -801,7 +805,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 3276; + CURRENT_PROJECT_VERSION = 3278; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Mythic/Preview Content\""; @@ -848,7 +852,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 3276; + CURRENT_PROJECT_VERSION = 3278; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Mythic/Preview Content\""; diff --git a/Mythic/Localizable.xcstrings b/Mythic/Localizable.xcstrings index 3abe34f..b26bfb4 100644 --- a/Mythic/Localizable.xcstrings +++ b/Mythic/Localizable.xcstrings @@ -6126,6 +6126,9 @@ } } } + }, + "Asynchronous DXVK" : { + }, "Automatically check for Mythic Engine updates" : { "localizations" : { @@ -11268,6 +11271,12 @@ } } } + }, + "DXVK" : { + + }, + "DXVK cannot be modified: %@" : { + }, "Engine" : { @@ -26707,6 +26716,9 @@ } } } + }, + "Quit games running in this container?" : { + }, "Remove" : { "localizations" : { @@ -35080,6 +35092,9 @@ } } } + }, + "To toggle DXVK, Mythic must quit all games currently running in this container.\nAdditionally, D3DMetal will be disabled.\n\nToggling DXVK may impact compatibility positively or negatively." : { + }, "Unable to locate %@ at its specified path (%@)" : { "localizations" : { diff --git a/Mythic/Utilities/Extensions/Built-in/FileManager.swift b/Mythic/Utilities/Extensions/Built-in/FileManager.swift new file mode 100644 index 0000000..7c2db9c --- /dev/null +++ b/Mythic/Utilities/Extensions/Built-in/FileManager.swift @@ -0,0 +1,16 @@ +// +// FileManager.swift +// Mythic +// +// Created by vapidinfinity (esi) on 15/1/2025. +// + +import Foundation + +extension FileManager { + func removeItemIfExists(at url: URL) throws { + if files.fileExists(atPath: url.path) { + try files.removeItem(at: url) + } + } +} diff --git a/Mythic/Utilities/Legendary/LegendaryInterface.swift b/Mythic/Utilities/Legendary/LegendaryInterface.swift index 1767b29..337619b 100644 --- a/Mythic/Utilities/Legendary/LegendaryInterface.swift +++ b/Mythic/Utilities/Legendary/LegendaryInterface.swift @@ -409,8 +409,16 @@ final class Legendary { if case .windows = game.platform { arguments += ["--wine", Engine.directory.appending(path: "wine/bin/wine64").path] environmentVariables["WINEPREFIX"] = container.url.path(percentEncoded: false) - environmentVariables["WINEMSYNC"] = container.settings.msync ? "1" : "0" - environmentVariables["ROSETTA_ADVERTISE_AVX"] = container.settings.avx2 ? "1" : "0" + environmentVariables["WINEMSYNC"] = "\(container.settings.msync.numericalValue)" + environmentVariables["ROSETTA_ADVERTISE_AVX"] = "\(container.settings.avx2.numericalValue)" + + if container.settings.dxvk { + environmentVariables["WINEDLLOVERRIDES"] = "d3d10core,d3d11=n,b" + + if container.settings.dxvkAsync { + environmentVariables["DXVK_ASYNC"] = "1" + } + } } arguments.append(contentsOf: ["--"] + game.launchArguments) diff --git a/Mythic/Utilities/Local/LocalGames.swift b/Mythic/Utilities/Local/LocalGames.swift index 589f9b8..c695994 100644 --- a/Mythic/Utilities/Local/LocalGames.swift +++ b/Mythic/Utilities/Local/LocalGames.swift @@ -77,17 +77,28 @@ final class LocalGames { guard Engine.exists else { throw Engine.NotInstalledError() } guard let containerURL = game.containerURL else { throw Wine.ContainerDoesNotExistError() } // FIXME: Container Revamp let container = try Wine.getContainerObject(url: containerURL) - + + var environmentVariables = [ + "MTL_HUD_ENABLED": "\(container.settings.metalHUD.numericalValue)", + "WINEMSYNC": "\(container.settings.msync.numericalValue)", + "ROSETTA_ADVERTISE_AVX": "\(container.settings.avx2.numericalValue)" + ] + + if container.settings.dxvk { + environmentVariables["WINEDLLOVERRIDES"] = "d3d10core,d3d11=n,b" + + if container.settings.dxvkAsync { + environmentVariables["DXVK_ASYNC"] = "1" + } + } + try await Wine.command( arguments: [game.path!] + game.launchArguments, identifier: "launch_\(game.title)", containerURL: container.url, - environment: [ - "MTL_HUD_ENABLED": container.settings.metalHUD ? "1" : "0", - "WINEMSYNC": container.settings.msync ? "1" : "0", - "ROSETTA_ADVERTISE_AVX": container.settings.avx2 ? "1" : "0", - ] - ) { _ in } + environment: environmentVariables, + completion: { _ in } + ) case .none: do { } // this should never happen diff --git a/Mythic/Utilities/Wine/WineInterface.swift b/Mythic/Utilities/Wine/WineInterface.swift index 0d8537e..91ad256 100644 --- a/Mythic/Utilities/Wine/WineInterface.swift +++ b/Mythic/Utilities/Wine/WineInterface.swift @@ -44,7 +44,7 @@ final class Wine { // TODO: https://forum.winehq.org/viewtopic.php?t=15416 } /// The directory where all wine prefixes/containers related to Mythic are stored. - static let containersDirectory: URL? = { + static var containersDirectory: URL? { let directory = Bundle.appContainer!.appending(path: "Containers") if files.fileExists(atPath: directory.path) { return directory @@ -58,8 +58,8 @@ final class Wine { // TODO: https://forum.winehq.org/viewtopic.php?t=15416 return nil } } - }() - + } + static var containerURLs: Set { get { return .init((try? defaults.decodeAndGet([URL].self, forKey: "containerURLs")) ?? []) diff --git a/Mythic/Views/Unified/Sheets/ContainerSettingsView.swift b/Mythic/Views/Unified/Sheets/ContainerSettingsView.swift index 5f8762b..83478cd 100644 --- a/Mythic/Views/Unified/Sheets/ContainerSettingsView.swift +++ b/Mythic/Views/Unified/Sheets/ContainerSettingsView.swift @@ -16,10 +16,8 @@ import SwiftUI +// TODO: refactor struct ContainerSettingsView: View { - // TODO: Add DXVK - // TODO: FSR 3? - @Binding var selectedContainerURL: URL? var withPicker: Bool @@ -30,7 +28,11 @@ struct ContainerSettingsView: View { @State private var retinaMode: Bool = Wine.ContainerSettings().retinaMode @State private var modifyingRetinaMode: Bool = true @State private var retinaModeError: Error? - + + @State private var isDXVKDisclaimerPresented: Bool = false + @State private var modifyingDXVK: Bool = false + @State private var dxvkError: Error? + @State private var windowsVersion: Wine.WindowsVersion = Wine.ContainerSettings().windowsVersion @State private var modifyingWindowsVersion: Bool = true @State private var windowsVersionError: Error? @@ -142,6 +144,100 @@ struct ContainerSettingsView: View { return "AVX2 is only supported on macOS Sequoia (15) or later." }()) + if !modifyingDXVK, dxvkError == nil { + Toggle("DXVK", isOn: Binding( + get: { container.settings.dxvk }, + set: { newValue in + isDXVKDisclaimerPresented = true + } + )) + .alert(isPresented: $isDXVKDisclaimerPresented) { + .init( + title: .init("Quit games running in this container?"), + message: .init(""" + To toggle DXVK, Mythic must quit all games currently running in this container. + Additionally, D3DMetal will be disabled. + + Toggling DXVK may impact compatibility positively or negatively. + """), + primaryButton: .default(.init("OK")) { + Task(priority: .userInitiated) { + do { + print("begin mod") + withAnimation { modifyingDXVK = true } + + print(container.url.appending(path: "drive_c/windows/system32/d3d10core.dll").path) + // x64 + try files.removeItemIfExists(at: container.url.appending(path: "drive_c/windows/system32/d3d10core.dll")) + try files.removeItemIfExists(at: container.url.appending(path: "drive_c/windows/system32/d3d11.dll")) + print("cleared x64") + + // x32 + try files.removeItemIfExists(at: container.url.appending(path: "drive_c/windows/syswow64/d3d10core.dll")) + try files.removeItemIfExists(at: container.url.appending(path: "drive_c/windows/syswow64/d3d11.dll")) + print("sayonara❤️") + + if container.settings.dxvk { + try await Wine.command( + arguments: ["wineboot", "-u"], + identifier: "dxvkRestore", + containerURL: container.url, + completion: { _ in } + ) + } else { + // x64 + try files.copyItem( + at: Engine.directory.appending(path: "DXVK/x64/d3d10core.dll"), + to: container.url.appending(path: "drive_c/windows/system32") + ) + try files.copyItem( + at: Engine.directory.appending(path: "DXVK/x64/d3d11.dll"), + to: container.url.appending(path: "drive_c/windows/system32") + ) + + // x32 + try files.copyItem( + at: Engine.directory.appending(path: "DXVK/x32/d3d10core.dll"), + to: container.url.appending(path: "drive_c/windows/syswow64") + ) + try files.copyItem( + at: Engine.directory.appending(path: "DXVK/x32/d3d11.dll"), + to: container.url.appending(path: "drive_c/windows/syswow64") + ) + } + + container.settings.dxvk.toggle() + withAnimation { modifyingDXVK = false } + } catch { + dxvkError = error + } + } + }, + secondaryButton: .cancel() + ) + } + } else { + HStack { + Text("DXVK") + Spacer() + if dxvkError == nil { + ProgressView() + .controlSize(.small) + } else { + Image(systemName: "exclamationmark.triangle") + .symbolVariant(.fill) + .controlSize(.small) + .help("DXVK cannot be modified: \(dxvkError?.localizedDescription ?? "Unknown Error.")") + } + } + } + + Toggle("Asynchronous DXVK", isOn: Binding( + get: { container.settings.dxvkAsync }, + set: { container.settings.dxvkAsync = $0 } + )) + .disabled(!container.settings.dxvk) + if !modifyingWindowsVersion, windowsVersionError == nil { Picker("Windows Version", selection: Binding( get: { windowsVersion },