diff --git a/Mythic/Utilities/Game/EpicGamesGame.swift b/Mythic/Utilities/Game/EpicGamesGame.swift index d86f640f..1b17cd8f 100644 --- a/Mythic/Utilities/Game/EpicGamesGame.swift +++ b/Mythic/Utilities/Game/EpicGamesGame.swift @@ -10,7 +10,7 @@ import Foundation import AppKit -class EpicGamesGame: Game { +class EpicGamesGame: Game, @unchecked Sendable { override var storefront: Storefront? { .epicGames } override var computedVerticalImageURL: URL? { Legendary.getImageURL(gameID: self.id, type: .tall) } diff --git a/Mythic/Utilities/GameManager/EpicGamesGameManager.swift b/Mythic/Utilities/GameManager/EpicGamesGameManager.swift index a929820a..daba739e 100644 --- a/Mythic/Utilities/GameManager/EpicGamesGameManager.swift +++ b/Mythic/Utilities/GameManager/EpicGamesGameManager.swift @@ -58,7 +58,7 @@ extension EpicGamesGameManager: StorefrontGameManager { guard case .epicGames = game.storefront, let castGame = game as? EpicGamesGame else { throw CocoaError(.coderInvalidValue) } - return try await Task(operation: { try await launch(game: castGame) }).value + return try await launch(game: castGame) } @MainActor static func move(game: Game, @@ -66,7 +66,7 @@ extension EpicGamesGameManager: StorefrontGameManager { guard case .epicGames = game.storefront, let castGame = game as? EpicGamesGame else { throw CocoaError(.coderInvalidValue) } - return try await Task(operation: { try await move(game: castGame, to: location) }).value + return try await move(game: castGame, to: location) } @MainActor static func uninstall(game: Game, diff --git a/Mythic/Utilities/GameManager/Legendary/LegendaryInterface.swift b/Mythic/Utilities/GameManager/Legendary/LegendaryInterface.swift index abecd938..e15e2aa8 100644 --- a/Mythic/Utilities/GameManager/Legendary/LegendaryInterface.swift +++ b/Mythic/Utilities/GameManager/Legendary/LegendaryInterface.swift @@ -56,6 +56,10 @@ final class Legendary { } static func handleCLIErrorOutput(fromStandardErrorOutput output: String) throws { + if output.contains("ValueError: No saved credentials") { + throw NotSignedInError() + } + for line in output.split(whereSeparator: \.isNewline) { if let match = try? Regex(#"(ERROR|CRITICAL): (.*)"#).firstMatch(in: line), let errorReason = match.last?.substring { @@ -103,7 +107,7 @@ final class Legendary { progress: Progress) { // these regexes are not dynamic, so there's no reason why they should fail to initialise // swiftlint:disable force_try - let progressRegex: Regex = try! .init(#"Progress: (?\d+\.\d+)% \((?\d+)\/(?\d+)\), Running for (?\d+:\d+:\d+), ETA: (?\d+:\d+:\d+)"#) + let progressRegex: Regex = try! .init(#"Progress: (?\d+(?:\.\d+)?)% \((?\d+)\/(?\d+)\), Running for (?\d+:\d+:\d+), ETA: (?(?:\d+:\d+:\d+|--:--:--|Unknown))"#) // let downloadRegex: Regex = try! .init(#"Downloaded: (?\d+\.\d+) \w+, Written: (?\d+\.\d+) \w+"#) // let cacheRegex: Regex = try! .init(#"Cache usage: (?\d+\.\d+) \w+, active tasks: (?\d+)"#) let downloadSpeedRegex: Regex = try! .init(#"\+ Download\s+- (?[\d.]+) \w+/\w+ \(raw\) / (?[\d.]+) \w+/\w+ \(decompressed\)"#) @@ -410,8 +414,10 @@ final class Legendary { throw CocoaError(.fileNoSuchFile) } + let destinationURL = newLocation.appending(path: currentLocation.lastPathComponent) + let operation: GameOperation = .init(game: game, type: .move) { _ in - try FileManager.default.moveItem(at: currentLocation, to: newLocation) + try FileManager.default.moveItem(at: currentLocation, to: destinationURL) let process: Process = .init() process.arguments = ["move", game.id, newLocation.path, "--skip-move"] @@ -424,7 +430,7 @@ final class Legendary { try handleCLIErrorOutput(fromStandardErrorPipe: processStandardErrorPipe) - game.installationState = .installed(location: newLocation, platform: platform) + game.installationState = .installed(location: destinationURL, platform: platform) } await Game.operationManager.queueOperation(operation) diff --git a/Mythic/Utilities/GameManager/LocalGameManager.swift b/Mythic/Utilities/GameManager/LocalGameManager.swift index 1f5483ef..b001c15c 100644 --- a/Mythic/Utilities/GameManager/LocalGameManager.swift +++ b/Mythic/Utilities/GameManager/LocalGameManager.swift @@ -91,6 +91,7 @@ class LocalGameManager { let process: Process = .init() process.arguments = [location.path] + game.launchArguments + process.currentDirectoryURL = location.deletingLastPathComponent() process.environment = environment Wine.transformProcess(process, containerURL: containerURL) @@ -111,10 +112,13 @@ class LocalGameManager { throw CocoaError(.fileNoSuchFile) } - let operation: GameOperation = .init(game: game, type: .uninstall) { _ in - try FileManager.default.moveItem(at: currentLocation, to: newLocation) - game.installationState = .installed(location: newLocation, platform: platform) + let destinationURL = newLocation.appending(path: currentLocation.lastPathComponent) + + let operation: GameOperation = .init(game: game, type: .move) { _ in + try FileManager.default.moveItem(at: currentLocation, to: destinationURL) + game.installationState = .installed(location: destinationURL, platform: platform) } + Game.operationManager.queueOperation(operation) return operation } diff --git a/Mythic/Views/Navigation/SupportView.swift b/Mythic/Views/Navigation/SupportView.swift index cb45653f..79733913 100644 --- a/Mythic/Views/Navigation/SupportView.swift +++ b/Mythic/Views/Navigation/SupportView.swift @@ -33,7 +33,7 @@ struct SupportView: View { } verticalDivider(height: 30) Button("FAQ"){ - openLink(urlString: "https://getmythic.app/faq/") + openLink(urlString: "https://docs.getmythic.app/docs/faq/") } verticalDivider(height: 30) Button("Compatibility List"){ diff --git a/Mythic/Views/Unified/Components/WebView.swift b/Mythic/Views/Unified/Components/WebView.swift index 6ad4a8e6..8c394330 100644 --- a/Mythic/Views/Unified/Components/WebView.swift +++ b/Mythic/Views/Unified/Components/WebView.swift @@ -27,11 +27,12 @@ struct WebView: NSViewRepresentable { let webView = WKWebView(frame: .zero, configuration: config) webView.navigationDelegate = context.coordinator + webView.load(URLRequest(url: url)) return webView } func updateNSView(_ nsView: WKWebView, context: Context) { - if nsView.url != self.url { + if nsView.url == nil || url.scheme == "javascript" { let request = URLRequest(url: self.url) nsView.load(request) } diff --git a/Mythic/Views/Unified/Windows/EpicWebAuthView.swift b/Mythic/Views/Unified/Windows/EpicWebAuthView.swift index 2ff40c69..61ed0c5e 100644 --- a/Mythic/Views/Unified/Windows/EpicWebAuthView.swift +++ b/Mythic/Views/Unified/Windows/EpicWebAuthView.swift @@ -161,13 +161,24 @@ private struct EpicInterceptorWebView: NSViewRepresentable { let completion: (String) -> Void - class Coordinator: NSObject, WKNavigationDelegate { + class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate { let parent: EpicInterceptorWebView init(parent: EpicInterceptorWebView) { self.parent = parent } + func webView(_ webView: WKWebView, + createWebViewWith configuration: WKWebViewConfiguration, + for navigationAction: WKNavigationAction, + windowFeatures: WKWindowFeatures) -> WKWebView? { + if navigationAction.targetFrame == nil { + webView.load(navigationAction.request) + } + + return nil + } + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { webView.evaluateJavaScript("document.body.innerText") { result, error in guard error == nil else { @@ -237,6 +248,7 @@ private struct EpicInterceptorWebView: NSViewRepresentable { let webView = WKWebView(frame: .zero, configuration: config) webView.navigationDelegate = context.coordinator + webView.uiDelegate = context.coordinator webView.load(URLRequest(url: URL(string: "https://legendary.gl/epiclogin")!)) return webView }