diff --git a/Common/Database/Model/Cheat.swift b/Common/Database/Model/Cheat.swift index 4adfc68..8e9baff 100644 --- a/Common/Database/Model/Cheat.swift +++ b/Common/Database/Model/Cheat.swift @@ -19,6 +19,7 @@ extension Cheat case name case code case type + case enabled case creationDate case modifiedDate @@ -55,6 +56,7 @@ class Cheat: NSManagedObject, CheatProtocol @NSManaged var name: String? @NSManaged var code: String @NSManaged var modifiedDate: NSDate + @NSManaged var enabled: Bool @NSManaged private(set) var identifier: String @NSManaged private(set) var creationDate: NSDate diff --git a/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents b/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents index 6e77d59..b03bbf7 100644 --- a/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents +++ b/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents @@ -3,6 +3,7 @@ + @@ -68,9 +69,9 @@ + - \ No newline at end of file diff --git a/Delta/Base.lproj/PauseMenu.storyboard b/Delta/Base.lproj/PauseMenu.storyboard index 352117b..d4bfab4 100644 --- a/Delta/Base.lproj/PauseMenu.storyboard +++ b/Delta/Base.lproj/PauseMenu.storyboard @@ -203,7 +203,9 @@ + + @@ -214,7 +216,7 @@ - + diff --git a/Delta/Emulation/EmulationViewController.swift b/Delta/Emulation/EmulationViewController.swift index 860ce35..cbed37c 100644 --- a/Delta/Emulation/EmulationViewController.swift +++ b/Delta/Emulation/EmulationViewController.swift @@ -106,17 +106,23 @@ class EmulationViewController: UIViewController self.deferredPreparationHandler?() self.deferredPreparationHandler = nil + // Yes, order DOES matter here, in order to prevent audio from being slightly delayed after peeking with 3D Touch (ugh so tired of that issue) + switch self.emulatorCore.state + { + case .Stopped: + self.emulatorCore.startEmulation() + self.updateCheats() + + case .Running: break + case .Paused: + self.updateCheats() + self.emulatorCore.resumeEmulation() + } + // Toggle audioManager.enabled to reset the audio buffer and ensure the audio isn't delayed from the beginning // This is especially noticeable when peeking a game self.emulatorCore.audioManager.enabled = false self.emulatorCore.audioManager.enabled = true - - switch self.emulatorCore.state - { - case .Stopped: self.emulatorCore.startEmulation() - case .Running: break - case .Paused: self.emulatorCore.resumeEmulation() - } } override func viewDidLayoutSubviews() @@ -181,22 +187,22 @@ class EmulationViewController: UIViewController // Specifically, if you pause a game, open the save states menu, go back, return to menu, select a new game, then try to pause it, it will crash // As a dirty workaround, we just use a weak reference, and force unwrap it if needed - let saveStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Save State", comment: ""), action: { [weak self] _ in - pauseViewController.presentSaveStateViewControllerWithMode(.Saving, delegate: self!) + let saveStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Save State", comment: ""), action: { [unowned self] _ in + pauseViewController.presentSaveStateViewControllerWithMode(.Saving, delegate: self) }) - let loadStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Load State", comment: ""), action: { [weak self] _ in - pauseViewController.presentSaveStateViewControllerWithMode(.Loading, delegate: self!) + let loadStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Load State", comment: ""), action: { [unowned self] _ in + pauseViewController.presentSaveStateViewControllerWithMode(.Loading, delegate: self) }) - let cheatCodesItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Cheat Codes", comment: ""), action: { _ in + let cheatCodesItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Cheat Codes", comment: ""), action: { [unowned self] _ in pauseViewController.presentCheatsViewController(delegate: self) }) let sustainButtonItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Sustain Button", comment: ""), action: dismissAction) - var fastForwardItem = PauseItem(image: UIImage(named: "FastForward")!, text: NSLocalizedString("Fast Forward", comment: ""), action: { [weak self] item in - self?.emulatorCore.fastForwarding = item.selected + var fastForwardItem = PauseItem(image: UIImage(named: "FastForward")!, text: NSLocalizedString("Fast Forward", comment: ""), action: { [unowned self] item in + self.emulatorCore.fastForwarding = item.selected }) fastForwardItem.selected = self.emulatorCore.fastForwarding @@ -324,6 +330,8 @@ extension EmulationViewController: SaveStatesViewControllerDelegate { self.emulatorCore.loadSaveState(saveState) + self.updateCheats() + self.pauseViewController?.dismiss() } } @@ -336,6 +344,70 @@ extension EmulationViewController: CheatsViewControllerDelegate { return self.emulatorCore.game as! Game } + + func cheatsViewController(cheatsViewController: CheatsViewController, didActivateCheat cheat: Cheat) throws + { + try self.emulatorCore.activateCheat(cheat) + } + + func cheatsViewController(cheatsViewController: CheatsViewController, didDeactivateCheat cheat: Cheat) throws + { + try self.emulatorCore.deactivateCheat(cheat) + } + + private func updateCheats() + { + let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + backgroundContext.performBlockAndWait { + + let running = (self.emulatorCore.state == .Running) + + if running + { + // Core MUST be paused when activating cheats, or else race conditions could crash the core + self.emulatorCore.pauseEmulation() + } + + let predicate = NSPredicate(format: "%K == %@", Cheat.Attributes.game.rawValue, self.emulatorCore.game as! Game) + + let cheats = Cheat.instancesWithPredicate(predicate, inManagedObjectContext: backgroundContext, type: Cheat.self) + for cheat in cheats + { + do + { + if cheat.enabled + { + try self.emulatorCore.activateCheat(cheat) + } + else + { + try self.emulatorCore.deactivateCheat(cheat) + } + } + catch EmulatorCore.CheatError.invalid + { + print("Invalid cheat:", cheat.name, cheat.code) + } + catch EmulatorCore.CheatError.doesNotExist + { + // Ignore this error, because we could be deactivating a cheat that hasn't yet been activated + print("Cheat does not exist:", cheat.name, cheat.code) + } + catch let error as NSError + { + print("Unknown Cheat Error:", error, cheat.name, cheat.code) + } + + } + + if running + { + self.emulatorCore.resumeEmulation() + } + + } + + } } //MARK: - - diff --git a/Delta/Pause Menu/Cheats/CheatsViewController.swift b/Delta/Pause Menu/Cheats/CheatsViewController.swift index d1c6a59..7567bbb 100644 --- a/Delta/Pause Menu/Cheats/CheatsViewController.swift +++ b/Delta/Pause Menu/Cheats/CheatsViewController.swift @@ -9,11 +9,15 @@ import UIKit import CoreData +import DeltaCore + import Roxas protocol CheatsViewControllerDelegate: class { func cheatsViewControllerActiveGame(saveStatesViewController: CheatsViewController) -> Game + func cheatsViewController(cheatsViewController: CheatsViewController, didActivateCheat cheat: Cheat) throws + func cheatsViewController(cheatsViewController: CheatsViewController, didDeactivateCheat cheat: Cheat) throws } class CheatsViewController: UITableViewController @@ -95,19 +99,158 @@ private extension CheatsViewController } } +//MARK: - Managing Cheats - +/// Managing Cheats +private extension CheatsViewController +{ + @IBAction func addCheat() + { + let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + backgroundContext.performBlock { + + var game = self.delegate.cheatsViewControllerActiveGame(self) + game = backgroundContext.objectWithID(game.objectID) as! Game + + let cheat = Cheat.insertIntoManagedObjectContext(backgroundContext) + cheat.game = game + cheat.name = "Unlimited Jumps" + cheat.code = "3E2C-AF6F" + cheat.type = .gameGenie + + do + { + try self.delegate.cheatsViewController(self, didActivateCheat: cheat) + backgroundContext.saveWithErrorLogging() + } + catch EmulatorCore.CheatError.invalid + { + dispatch_async(dispatch_get_main_queue()) { + + let alertController = UIAlertController(title: NSLocalizedString("Invalid Cheat", comment: ""), message: NSLocalizedString("Please make sure you typed the cheat code in correctly and try again.", comment: ""), preferredStyle: .Alert) + alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .Default, handler: nil)) + self.presentViewController(alertController, animated: true, completion: nil) + + } + + print("Invalid cheat:", cheat.name, cheat.code) + } + catch let error as NSError + { + print("Unknown Cheat Error:", error, cheat.name, cheat.code) + } + + + } + } + + func deleteCheat(cheat: Cheat) + { + let _ = try? self.delegate.cheatsViewController(self, didDeactivateCheat: cheat) + + let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + backgroundContext.performBlock { + let temporaryCheat = backgroundContext.objectWithID(cheat.objectID) + backgroundContext.deleteObject(temporaryCheat) + backgroundContext.saveWithErrorLogging() + } + } +} + +//MARK: - Content - +/// Content +private extension CheatsViewController +{ + func configure(cell cell: UITableViewCell, forIndexPath indexPath: NSIndexPath) + { + let cheat = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Cheat + cell.textLabel?.text = cheat.name + cell.textLabel?.font = UIFont.boldSystemFontOfSize(cell.textLabel!.font.pointSize) + cell.accessoryType = cheat.enabled ? .Checkmark : .None + } +} + extension CheatsViewController { // MARK: - Table view data source override func numberOfSectionsInTableView(tableView: UITableView) -> Int { - // #warning Incomplete implementation, return the number of sections - return 0 + let numberOfSections = self.fetchedResultsController.sections!.count + return numberOfSections } - override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // #warning Incomplete implementation, return the number of rows - return 0 + override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int + { + let section = self.fetchedResultsController.sections![section] + return section.numberOfObjects + } + + override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell + { + let cell = tableView.dequeueReusableCellWithIdentifier(RSTGenericCellIdentifier, forIndexPath: indexPath) + self.configure(cell: cell, forIndexPath: indexPath) + return cell + } +} + +extension CheatsViewController +{ + override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) + { + let cheat = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Cheat + + let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext() + backgroundContext.performBlockAndWait { + let temporaryCheat = backgroundContext.objectWithID(cheat.objectID) as! Cheat + temporaryCheat.enabled = !temporaryCheat.enabled + + do + { + if temporaryCheat.enabled + { + try self.delegate.cheatsViewController(self, didActivateCheat: temporaryCheat) + } + else + { + try self.delegate.cheatsViewController(self, didDeactivateCheat: temporaryCheat) + } + } + catch EmulatorCore.CheatError.invalid + { + print("Invalid cheat:", cheat.name, cheat.code) + } + catch EmulatorCore.CheatError.doesNotExist + { + print("Cheat does not exist:", cheat.name, cheat.code) + } + catch let error as NSError + { + print("Unknown Cheat Error:", error, cheat.name, cheat.code) + } + + backgroundContext.saveWithErrorLogging() + } + + self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None) + } + + override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? + { + let deleteAction = UITableViewRowAction(style: .Destructive, title: NSLocalizedString("Delete", comment: "")) { (action, indexPath) in + let cheat = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Cheat + self.deleteCheat(cheat) + } + + let editAction = UITableViewRowAction(style: .Normal, title: NSLocalizedString("Edit", comment: "")) { (action, indexPath) in + + } + + return [deleteAction, editAction] + } + + override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) + { + // This method intentionally left blank because someone decided it was a Good Idea™ to require this method be implemented to use UITableViewRowActions } }