diff --git a/Cores/DeltaCore b/Cores/DeltaCore index 911d36b..8e6c824 160000 --- a/Cores/DeltaCore +++ b/Cores/DeltaCore @@ -1 +1 @@ -Subproject commit 911d36bd01964ad7aeb0b4a37610402fd9845310 +Subproject commit 8e6c824c3bc847cc4749e53256b42f9a9207c20f diff --git a/Cores/GBCDeltaCore b/Cores/GBCDeltaCore index 6da27f9..4734740 160000 --- a/Cores/GBCDeltaCore +++ b/Cores/GBCDeltaCore @@ -1 +1 @@ -Subproject commit 6da27f9b694e886d6c9805ba54848b0f0a922598 +Subproject commit 473474019bd2e3be623cd593da4c1e8c4e51fb80 diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index 4c3449a..0a34271 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -129,6 +129,8 @@ BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */; }; BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */; }; BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */; }; + BFE593CA21F3F8B7003412A6 /* GameSave.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE593C921F3F8B7003412A6 /* GameSave.swift */; }; + BFE593CC21F3F8C2003412A6 /* _GameSave.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE593CB21F3F8C2003412A6 /* _GameSave.swift */; }; BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; }; BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */; }; @@ -281,6 +283,8 @@ BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollectionViewController.swift; sourceTree = ""; }; BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverMenuButton.swift; sourceTree = ""; }; BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveStatesStoryboardSegue.swift; sourceTree = ""; }; + BFE593C921F3F8B7003412A6 /* GameSave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameSave.swift; sourceTree = ""; }; + BFE593CB21F3F8C2003412A6 /* _GameSave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _GameSave.swift; sourceTree = ""; }; BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveStateMigrationPolicy.swift; sourceTree = ""; }; BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBCDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -471,6 +475,7 @@ BF5942791E09BC830051894B /* Game.swift */, BF59427A1E09BC830051894B /* GameCollection.swift */, BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */, + BFE593C921F3F8B7003412A6 /* GameSave.swift */, BF59427B1E09BC830051894B /* SaveState.swift */, ); path = Human; @@ -484,6 +489,7 @@ BF5942831E09BC8B0051894B /* _Game.swift */, BF5942841E09BC8B0051894B /* _GameCollection.swift */, BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */, + BFE593CB21F3F8C2003412A6 /* _GameSave.swift */, BF5942851E09BC8B0051894B /* _SaveState.swift */, ); path = Machine; @@ -957,6 +963,7 @@ BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */, BF8A334621A4926F00A42FD4 /* GameSyncStatusViewController.swift in Sources */, BF59427E1E09BC830051894B /* Game.swift in Sources */, + BFE593CC21F3F8C2003412A6 /* _GameSave.swift in Sources */, BF63A1A321A4AAAE00EE8F61 /* RecordSyncStatusViewController.swift in Sources */, BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */, BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */, @@ -965,6 +972,7 @@ BF59428E1E09BCFB0051894B /* ImportController.swift in Sources */, BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */, BF48F74E219A16DA00BC2FC1 /* SyncingServicesViewController.swift in Sources */, + BFE593CA21F3F8B7003412A6 /* GameSave.swift in Sources */, BF63A1B521A4B76E00EE8F61 /* RecordVersionsViewController.swift in Sources */, BF6424831F5B8F3F00D6AB44 /* ListMenuViewController.swift in Sources */, BF1020E31F95B05B00313182 /* DeltaToDelta2.xcmappingmodel in Sources */, diff --git a/Delta/Database/Model/Delta.xcdatamodeld/Delta 2.xcdatamodel/contents b/Delta/Database/Model/Delta.xcdatamodeld/Delta 2.xcdatamodel/contents index 20648c4..283c105 100644 --- a/Delta/Database/Model/Delta.xcdatamodeld/Delta 2.xcdatamodel/contents +++ b/Delta/Database/Model/Delta.xcdatamodeld/Delta 2.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -62,6 +62,7 @@ + @@ -109,6 +110,16 @@ + + + + + + + + + + @@ -135,9 +146,10 @@ - + + \ No newline at end of file diff --git a/Delta/Database/Model/Human/GameSave.swift b/Delta/Database/Model/Human/GameSave.swift new file mode 100644 index 0000000..da86b8b --- /dev/null +++ b/Delta/Database/Model/Human/GameSave.swift @@ -0,0 +1,60 @@ +// +// GameSave.swift +// Delta +// +// Created by Riley Testut on 8/30/16. +// Copyright (c) 2016 Riley Testut. All rights reserved. +// + +import Foundation + +import Harmony + +@objc(GameSave) +public class GameSave: _GameSave +{ + public override func awakeFromInsert() + { + super.awakeFromInsert() + + self.modifiedDate = Date() + } +} + +extension GameSave: Syncable +{ + public static var syncablePrimaryKey: AnyKeyPath { + return \GameSave.identifier + } + + public var syncableKeys: Set { + return [\GameSave.modifiedDate] + } + + public var syncableRelationships: Set { + return [\GameSave.game] + } + + public var syncableFiles: Set { + guard let game = self.game else { return [] } + + var files: Set = [File(identifier: "gameSave", fileURL: game.gameSaveURL)] + + if game.type == .gbc + { + let gameTimeSaveURL = game.gameSaveURL.deletingPathExtension().appendingPathExtension("rtc") + files.insert(File(identifier: "gameTimeSave", fileURL: gameTimeSaveURL)) + } + + return files + } + + public var syncableMetadata: [HarmonyMetadataKey : String] { + guard let game = self.game else { return [:] } + return [.gameID: game.identifier, .gameName: game.name] + } + + public var syncableLocalizedName: String? { + return self.game?.name + } +} diff --git a/Delta/Database/Model/Machine/_Game.swift b/Delta/Database/Model/Machine/_Game.swift index a1fb05f..112510b 100644 --- a/Delta/Database/Model/Machine/_Game.swift +++ b/Delta/Database/Model/Machine/_Game.swift @@ -32,6 +32,8 @@ public class _Game: NSManagedObject @NSManaged public var gameCollection: GameCollection? + @NSManaged public var gameSave: GameSave? + @NSManaged public var previewSaveState: SaveState? @NSManaged public var saveStates: Set diff --git a/Delta/Database/Model/Machine/_GameSave.swift b/Delta/Database/Model/Machine/_GameSave.swift new file mode 100644 index 0000000..04dfe45 --- /dev/null +++ b/Delta/Database/Model/Machine/_GameSave.swift @@ -0,0 +1,26 @@ +// DO NOT EDIT. This file is machine-generated and constantly overwritten. +// Make changes to GameSave.swift instead. + +import Foundation +import CoreData + +import DeltaCore + +public class _GameSave: NSManagedObject +{ + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "GameSave") + } + + // MARK: - Properties + + @NSManaged public var identifier: String + + @NSManaged public var modifiedDate: Date + + // MARK: - Relationships + + @NSManaged public var game: Game? + +} + diff --git a/Delta/Emulation/GameViewController.swift b/Delta/Emulation/GameViewController.swift index a2a0cac..9b1d3a9 100644 --- a/Delta/Emulation/GameViewController.swift +++ b/Delta/Emulation/GameViewController.swift @@ -66,6 +66,8 @@ class GameViewController: DeltaCore.GameViewController let game = self.game as? Game NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.managedObjectContextDidChange(with:)), name: .NSManagedObjectContextObjectsDidChange, object: game?.managedObjectContext) + self.emulatorCore?.saveHandler = { [weak self] _ in self?.updateGameSave() } + self.updateControllerSkin() self.updateControllers() } @@ -501,6 +503,40 @@ private extension GameViewController } } +//MARK: - Game Saves - +/// Game Saves +private extension GameViewController +{ + func updateGameSave() + { + guard let game = self.game as? Game else { return } + + DatabaseManager.shared.performBackgroundTask { (context) in + let game = context.object(with: game.objectID) as! Game + + if let gameSave = game.gameSave + { + gameSave.modifiedDate = Date() + } + else + { + let gameSave = GameSave(context: context) + gameSave.identifier = game.identifier + game.gameSave = gameSave + } + + do + { + try context.save() + } + catch + { + print("Error updating game save.", error) + } + } + } +} + //MARK: - Save States - /// Save States extension GameViewController: SaveStatesViewControllerDelegate diff --git a/Delta/Settings/Syncing/GameSyncStatusViewController.swift b/Delta/Settings/Syncing/GameSyncStatusViewController.swift index dac324f..45a05d6 100644 --- a/Delta/Settings/Syncing/GameSyncStatusViewController.swift +++ b/Delta/Settings/Syncing/GameSyncStatusViewController.swift @@ -85,11 +85,18 @@ private extension GameSyncStatusViewController } } - let gameDataSource = RSTArrayTableViewDataSource(items: [self.game]) - gameDataSource.cellConfigurationHandler = { (cell, game, indexPath) in - cell.textLabel?.text = NSLocalizedString("Game", comment: "") + let gameDataSource = RSTArrayTableViewDataSource(items: [self.game, self.game.gameSave].compactMap { $0 }) + gameDataSource.cellConfigurationHandler = { (cell, item, indexPath) in + if item is Game + { + cell.textLabel?.text = NSLocalizedString("Game", comment: "") + } + else + { + cell.textLabel?.text = NSLocalizedString("Game Save", comment: "") + } - configure(cell, recordedObject: game) + configure(cell, recordedObject: item) } let saveStatesFetchRequest = SaveState.fetchRequest() as NSFetchRequest @@ -129,7 +136,7 @@ private extension GameSyncStatusViewController do { - let recordedObjects = ([self.game!] + Array(self.game.saveStates) + Array(self.game.cheats)) as! [SyncableManagedObject] + let recordedObjects = ([self.game, self.game.gameSave].compactMap { $0 } + Array(self.game.saveStates) + Array(self.game.cheats)) as! [SyncableManagedObject] let records = try SyncManager.shared.recordController.fetchRecords(for: recordedObjects) for record in records @@ -156,7 +163,7 @@ extension GameSyncStatusViewController switch Section.allCases[section] { - case .game: return NSLocalizedString("Game", comment: "") + case .game: return nil case .saveStates: return NSLocalizedString("Save States", comment: "") case .cheats: return NSLocalizedString("Cheats", comment: "") } diff --git a/Delta/Settings/Syncing/SyncStatusViewController.swift b/Delta/Settings/Syncing/SyncStatusViewController.swift index 4a569f4..8990f72 100644 --- a/Delta/Settings/Syncing/SyncStatusViewController.swift +++ b/Delta/Settings/Syncing/SyncStatusViewController.swift @@ -129,6 +129,7 @@ private extension SyncStatusViewController case let game as Game: conflictedGame = game case let saveState as SaveState: conflictedGame = saveState.game case let cheat as Cheat: conflictedGame = cheat.game + case let gameSave as GameSave: conflictedGame = gameSave.game default: conflictedGame = nil } diff --git a/Delta/Syncing/SyncManager.swift b/Delta/Syncing/SyncManager.swift index 24ddd12..077f0cd 100644 --- a/Delta/Syncing/SyncManager.swift +++ b/Delta/Syncing/SyncManager.swift @@ -19,6 +19,7 @@ extension SyncManager case saveState = "SaveState" case controllerSkin = "ControllerSkin" case gameControllerInputMapping = "GameControllerInputMapping" + case gameSave = "GameSave" var localizedName: String { switch self @@ -29,6 +30,7 @@ extension SyncManager case .saveState: return NSLocalizedString("Save State", comment: "") case .controllerSkin: return NSLocalizedString("Controller Skin", comment: "") case .gameControllerInputMapping: return NSLocalizedString("Game Controller Input Mapping", comment: "") + case .gameSave: return NSLocalizedString("Game Save", comment: "") } } } diff --git a/Delta/Syncing/SyncResultViewController.swift b/Delta/Syncing/SyncResultViewController.swift index 6774056..c90d5b4 100644 --- a/Delta/Syncing/SyncResultViewController.swift +++ b/Delta/Syncing/SyncResultViewController.swift @@ -119,6 +119,7 @@ private extension SyncResultViewController switch recordType { case .game: title = NSLocalizedString("Game", comment: "") + case .gameSave: title = NSLocalizedString("Game Save", comment: "") case .saveState, .cheat, .controllerSkin, .gameCollection, .gameControllerInputMapping: title = error.record.localizedName ?? recordType.localizedName } @@ -210,6 +211,7 @@ private extension SyncResultViewController switch recordType { case .game: group = .game(error.record.recordID) + case .gameSave: group = .game(error.record.recordID) case .gameCollection: group = .gameCollection case .controllerSkin: group = .controllerSkin case .gameControllerInputMapping: group = .gameControllerInputMapping diff --git a/External/Harmony b/External/Harmony index 920a343..935be63 160000 --- a/External/Harmony +++ b/External/Harmony @@ -1 +1 @@ -Subproject commit 920a34311389cd4e3248b62f27f594058539b28b +Subproject commit 935be63ed805013b8e16191036720b5e32be55e7