From a1a578a1e0efdffb96424c6fe3232ae18e816de5 Mon Sep 17 00:00:00 2001 From: Brunelli Cupello Date: Fri, 21 Aug 2026 15:36:30 -0300 Subject: [PATCH] fix: two GameManager defects that make protocol dispatch unusable `EpicGamesGameManager.install(game:qualityOfService:)` recursed until the stack died. The typed overload requires `forPlatform:` and offers no default, so the call inside the witness re-selected the witness itself (`EpicGamesGame` is a `Game`). `StorefrontGameManager.install` is therefore unusable for Epic today; the UI only works because `EpicGamesGameInstallationView` calls the five-argument overload directly. The witness now resolves a platform from `getSupportedPlatforms()`, preferring Windows. `LocalGameManager.move(game:to:)` built its operation with type `.uninstall` and never called `queueOperation`, so the returned operation was inert -- its `function` never ran and the move silently did nothing. Co-Authored-By: Claude Opus 5 --- .../GameManager/EpicGamesGameManager.swift | 15 ++++++++++++++- .../Utilities/GameManager/LocalGameManager.swift | 4 +++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Mythic/Utilities/GameManager/EpicGamesGameManager.swift b/Mythic/Utilities/GameManager/EpicGamesGameManager.swift index a929820a..763d9f51 100644 --- a/Mythic/Utilities/GameManager/EpicGamesGameManager.swift +++ b/Mythic/Utilities/GameManager/EpicGamesGameManager.swift @@ -23,7 +23,20 @@ extension EpicGamesGameManager: StorefrontGameManager { guard case .epicGames = game.storefront, let castGame = game as? EpicGamesGame else { throw CocoaError(.coderInvalidValue) } - return try await install(game: castGame, qualityOfService: qualityOfService) + // The typed overload requires `forPlatform:` and offers no default for it, so + // `install(game: castGame, qualityOfService:)` re-selects *this* witness (EpicGamesGame is a + // Game) and recurses until the stack dies. Resolve a platform explicitly instead. + // Windows wins when both are offered: it is the superset in practice, and Mythic runs it + // through Wine either way. + let supportedPlatforms = castGame.getSupportedPlatforms() ?? .init() + let resolvedPlatform: Game.Platform? = supportedPlatforms.contains(.windows) + ? .windows + : supportedPlatforms.first + guard let resolvedPlatform else { throw CocoaError(.coderInvalidValue) } + + return try await install(game: castGame, + forPlatform: resolvedPlatform, + qualityOfService: qualityOfService) } static func update(game: Game, qualityOfService: QualityOfService) async throws -> GameOperation { diff --git a/Mythic/Utilities/GameManager/LocalGameManager.swift b/Mythic/Utilities/GameManager/LocalGameManager.swift index 1f5483ef..d1a80c8a 100644 --- a/Mythic/Utilities/GameManager/LocalGameManager.swift +++ b/Mythic/Utilities/GameManager/LocalGameManager.swift @@ -111,10 +111,12 @@ class LocalGameManager { throw CocoaError(.fileNoSuchFile) } - let operation: GameOperation = .init(game: game, type: .uninstall) { _ in + let operation: GameOperation = .init(game: game, type: .move) { _ in try FileManager.default.moveItem(at: currentLocation, to: newLocation) game.installationState = .installed(location: newLocation, platform: platform) } + + Game.operationManager.queueOperation(operation) return operation }