diff --git a/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents b/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents
index 8d95c92..2b5be3b 100644
--- a/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents
+++ b/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents
@@ -7,7 +7,7 @@
-
+
diff --git a/Cores/DeltaCore b/Cores/DeltaCore
index d819614..7149d73 160000
--- a/Cores/DeltaCore
+++ b/Cores/DeltaCore
@@ -1 +1 @@
-Subproject commit d819614e5fe422aa4975f70e3d4f1d2dc97b9c24
+Subproject commit 7149d73128c0f5c95b3e58990f9d185cf33277df
diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj
index f936360..f749d2f 100644
--- a/Delta.xcodeproj/project.pbxproj
+++ b/Delta.xcodeproj/project.pbxproj
@@ -19,6 +19,7 @@
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; };
BF27CC971BCC890700A20D89 /* GamesCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */; };
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */; };
+ BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF31878A1D489AAA00BD020D /* CheatValidator.swift */; };
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34FA061CF0F510006624C7 /* EditCheatViewController.swift */; };
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34FA101CF1899D006624C7 /* CheatTextView.swift */; };
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; };
@@ -102,6 +103,7 @@
BF27CC941BCB7B7A00A20D89 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.0.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; };
BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesCollectionViewController.swift; sourceTree = ""; };
BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesCollectionHeaderView.swift; path = "Pause Menu/Save States/SaveStatesCollectionHeaderView.swift"; sourceTree = ""; };
+ BF31878A1D489AAA00BD020D /* CheatValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatValidator.swift; path = "Pause Menu/Cheats/CheatValidator.swift"; sourceTree = ""; };
BF34FA061CF0F510006624C7 /* EditCheatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EditCheatViewController.swift; path = "Pause Menu/Cheats/EditCheatViewController.swift"; sourceTree = ""; };
BF34FA101CF1899D006624C7 /* CheatTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatTextView.swift; path = "Pause Menu/Cheats/CheatTextView.swift"; sourceTree = ""; };
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseViewController.swift; path = "Pause Menu/PauseViewController.swift"; sourceTree = ""; };
@@ -320,6 +322,7 @@
BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */,
BF34FA061CF0F510006624C7 /* EditCheatViewController.swift */,
BF34FA101CF1899D006624C7 /* CheatTextView.swift */,
+ BF31878A1D489AAA00BD020D /* CheatValidator.swift */,
);
name = Cheats;
sourceTree = "";
@@ -542,6 +545,7 @@
BF7AE8241C2E984300B1B5BC /* GridCollectionViewLayout.swift in Sources */,
BF1FB1861C5EE643007E2494 /* SaveState.swift in Sources */,
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */,
+ BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */,
BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */,
BFA2315C1CED10BE0011E35A /* Action.swift in Sources */,
BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */,
diff --git a/Delta/Emulation/GameViewController.swift b/Delta/Emulation/GameViewController.swift
index 7698503..a2d1422 100644
--- a/Delta/Emulation/GameViewController.swift
+++ b/Delta/Emulation/GameViewController.swift
@@ -10,13 +10,20 @@ import UIKit
import DeltaCore
+private var kvoContext = 0
+
class GameViewController: DeltaCore.GameViewController
{
/// Assumed to be Delta.Game instance
override var game: GameProtocol? {
+ willSet {
+ self.emulatorCore?.removeObserver(self, forKeyPath: #keyPath(EmulatorCore.state), context: &kvoContext)
+ }
didSet {
guard let emulatorCore = self.emulatorCore else { return }
self.preferredContentSize = emulatorCore.preferredRenderingSize
+
+ emulatorCore.addObserver(self, forKeyPath: #keyPath(EmulatorCore.state), options: [.old], context: &kvoContext)
}
}
@@ -51,6 +58,11 @@ class GameViewController: DeltaCore.GameViewController
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidDisconnect, object: nil)
}
+ deinit
+ {
+ self.emulatorCore?.removeObserver(self, forKeyPath: #keyPath(EmulatorCore.state), context: &kvoContext)
+ }
+
// MARK: GameControllerReceiver -
override func gameController(_ gameController: GameController, didActivate input: Input)
{
@@ -116,6 +128,7 @@ extension GameViewController
pauseViewController.pauseText = (self.game as? Game)?.name ?? NSLocalizedString("Delta", comment: "")
pauseViewController.emulatorCore = self.emulatorCore
pauseViewController.saveStatesViewControllerDelegate = self
+ pauseViewController.cheatsViewControllerDelegate = self
self.pauseViewController = pauseViewController
}
@@ -135,6 +148,20 @@ extension GameViewController
}
}
}
+
+ // MARK: - KVO -
+ /// KVO
+ override func observeValue(forKeyPath keyPath: String?, of object: AnyObject?, change: [NSKeyValueChangeKey : AnyObject]?, context: UnsafeMutablePointer?)
+ {
+ guard context == &kvoContext else { return super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) }
+
+ guard let rawValue = change?[.oldKey] as? Int, let previousState = EmulatorCore.State(rawValue: rawValue) else { return }
+
+ if previousState == .stopped
+ {
+ self.updateCheats()
+ }
+ }
}
//MARK: Controllers -
@@ -244,10 +271,81 @@ extension GameViewController: SaveStatesViewControllerDelegate
print(error)
}
+ self.updateCheats()
+
self.pauseViewController?.dismiss()
}
}
+//MARK: - Cheats
+/// Cheats
+extension GameViewController: CheatsViewControllerDelegate
+{
+ func cheatsViewController(_ cheatsViewController: CheatsViewController, activateCheat cheat: Cheat)
+ {
+ self.activate(cheat)
+ }
+
+ func cheatsViewController(_ cheatsViewController: CheatsViewController, deactivateCheat cheat: Cheat)
+ {
+ self.emulatorCore?.deactivate(cheat)
+ }
+
+ private func activate(_ cheat: Cheat)
+ {
+ do
+ {
+ try self.emulatorCore?.activate(cheat)
+ }
+ catch EmulatorCore.CheatError.invalid
+ {
+ print("Invalid cheat:", cheat.name, cheat.code)
+ }
+ catch let error as NSError
+ {
+ print("Unknown Cheat Error:", error, cheat.name, cheat.code)
+ }
+ }
+
+ private func updateCheats()
+ {
+ guard let game = self.game as? Game else { return }
+
+ let running = (self.emulatorCore?.state == .running)
+
+ if running
+ {
+ // Core MUST be paused when activating cheats, or else race conditions could crash the core
+ self.pauseEmulation()
+ }
+
+ let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
+ backgroundContext.performAndWait {
+
+ let predicate = Predicate(format: "%K == %@", Cheat.Attributes.game.rawValue, game)
+
+ let cheats = Cheat.instancesWithPredicate(predicate, inManagedObjectContext: backgroundContext, type: Cheat.self)
+ for cheat in cheats
+ {
+ if cheat.enabled
+ {
+ self.activate(cheat)
+ }
+ else
+ {
+ self.emulatorCore?.deactivate(cheat)
+ }
+ }
+ }
+
+ if running
+ {
+ self.resumeEmulation()
+ }
+
+ }
+}
+
//MARK: GameViewControllerDelegate -
/// GameViewControllerDelegate
extension GameViewController: GameViewControllerDelegate
diff --git a/Delta/Pause Menu/Cheats/CheatValidator.swift b/Delta/Pause Menu/Cheats/CheatValidator.swift
new file mode 100644
index 0000000..0c2593d
--- /dev/null
+++ b/Delta/Pause Menu/Cheats/CheatValidator.swift
@@ -0,0 +1,59 @@
+//
+// CheatValidator.swift
+// Delta
+//
+// Created by Riley Testut on 7/27/16.
+// Copyright © 2016 Riley Testut. All rights reserved.
+//
+
+import Foundation
+
+import DeltaCore
+
+extension CheatValidator
+{
+ enum Error: ErrorProtocol
+ {
+ case invalidCode
+ case invalidName
+ case duplicateName
+ case duplicateCode
+ }
+}
+
+struct CheatValidator
+{
+ let format: CheatFormat
+ let managedObjectContext: NSManagedObjectContext
+
+ func validate(_ cheat: Cheat) throws
+ {
+ guard let name = cheat.name else { throw Error.invalidName }
+
+ let code = cheat.code
+
+ // Find all cheats that are for the same game, don't have the same identifier as the current cheat, but have either the same name or code
+ let predicate = Predicate(format: "%K == %@ AND %K != %@ AND (%K == %@ OR %K == %@)", Cheat.Attributes.game.rawValue, cheat.game, Cheat.Attributes.identifier.rawValue, cheat.identifier, Cheat.Attributes.code.rawValue, code, Cheat.Attributes.name.rawValue, name)
+
+ let cheats = Cheat.instancesWithPredicate(predicate, inManagedObjectContext: self.managedObjectContext, type: Cheat.self)
+ for cheat in cheats
+ {
+ if cheat.name == name
+ {
+ throw Error.duplicateName
+ }
+ else if cheat.code == code
+ {
+ throw Error.duplicateCode
+ }
+ }
+
+ // Remove newline characters (code should already be formatted)
+ let sanitizedCode = (cheat.code as NSString).replacingOccurrences(of: "\n", with: "")
+
+ if sanitizedCode.characters.count % self.format.format.characters.count != 0
+ {
+ throw Error.invalidCode
+ }
+ }
+}
diff --git a/Delta/Pause Menu/Cheats/CheatsViewController.swift b/Delta/Pause Menu/Cheats/CheatsViewController.swift
index c0cc69c..1b6b03e 100644
--- a/Delta/Pause Menu/Cheats/CheatsViewController.swift
+++ b/Delta/Pause Menu/Cheats/CheatsViewController.swift
@@ -15,19 +15,20 @@ import Roxas
protocol CheatsViewControllerDelegate: class
{
- func cheatsViewControllerActiveEmulatorCore(_ saveStatesViewController: CheatsViewController) -> EmulatorCore
- func cheatsViewController(_ cheatsViewController: CheatsViewController, didActivateCheat cheat: Cheat) throws
- func cheatsViewController(_ cheatsViewController: CheatsViewController, didDeactivateCheat cheat: Cheat)
+ func cheatsViewController(_ cheatsViewController: CheatsViewController, activateCheat cheat: Cheat)
+ func cheatsViewController(_ cheatsViewController: CheatsViewController, deactivateCheat cheat: Cheat)
}
class CheatsViewController: UITableViewController
{
- weak var delegate: CheatsViewControllerDelegate! {
+ var game: Game! {
didSet {
self.updateFetchedResultsController()
}
}
+ weak var delegate: CheatsViewControllerDelegate?
+
private var backgroundView: RSTBackgroundView!
private var fetchedResultsController: NSFetchedResultsController!
@@ -83,11 +84,9 @@ private extension CheatsViewController
{
func updateFetchedResultsController()
{
- let game = self.delegate.cheatsViewControllerActiveEmulatorCore(self).game as! Game
-
let fetchRequest = Cheat.rst_fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
- fetchRequest.predicate = Predicate(format: "%K == %@", Cheat.Attributes.game.rawValue, game)
+ fetchRequest.predicate = Predicate(format: "%K == %@", Cheat.Attributes.game.rawValue, self.game)
fetchRequest.sortDescriptors = [SortDescriptor(key: Cheat.Attributes.name.rawValue, ascending: true)]
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.sharedManager.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
@@ -121,7 +120,7 @@ private extension CheatsViewController
func deleteCheat(_ cheat: Cheat)
{
- self.delegate.cheatsViewController(self, didDeactivateCheat: cheat)
+ self.delegate?.cheatsViewController(self, deactivateCheat: cheat)
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
backgroundContext.perform {
@@ -145,12 +144,11 @@ private extension CheatsViewController
}
func makeEditCheatViewController(cheat: Cheat?) -> EditCheatViewController
- {
+ {
let editCheatViewController = self.storyboard!.instantiateViewController(withIdentifier: "editCheatViewController") as! EditCheatViewController
editCheatViewController.delegate = self
- editCheatViewController.supportedCheatFormats = self.delegate.cheatsViewControllerActiveEmulatorCore(self).configuration.supportedCheatFormats
editCheatViewController.cheat = cheat
- editCheatViewController.game = self.delegate.cheatsViewControllerActiveEmulatorCore(self).game as! Game
+ editCheatViewController.game = self.game
return editCheatViewController
}
@@ -193,25 +191,13 @@ extension CheatsViewController
if temporaryCheat.enabled
{
- do
- {
- try self.delegate.cheatsViewController(self, didActivateCheat: temporaryCheat)
- }
- catch EmulatorCore.CheatError.invalid
- {
- print("Invalid cheat:", cheat.name, cheat.code)
- }
- catch let error as NSError
- {
- print("Unknown Cheat Error:", error, cheat.name, cheat.code)
- }
+ self.delegate?.cheatsViewController(self, activateCheat: temporaryCheat)
}
else
{
- self.delegate.cheatsViewController(self, didDeactivateCheat: temporaryCheat)
+ self.delegate?.cheatsViewController(self, deactivateCheat: temporaryCheat)
}
-
backgroundContext.saveWithErrorLogging()
}
@@ -266,9 +252,9 @@ extension CheatsViewController: UIViewControllerPreviewingDelegate
//MARK: - -
extension CheatsViewController: EditCheatViewControllerDelegate
{
- func editCheatViewController(_ editCheatViewController: EditCheatViewController, activateCheat cheat: Cheat, previousCheat: Cheat?) throws
+ func editCheatViewController(_ editCheatViewController: EditCheatViewController, activateCheat cheat: Cheat, previousCheat: Cheat?)
{
- try self.delegate.cheatsViewController(self, didActivateCheat: cheat)
+ self.delegate?.cheatsViewController(self, activateCheat: cheat)
if let previousCheat = previousCheat
{
@@ -278,14 +264,14 @@ extension CheatsViewController: EditCheatViewControllerDelegate
guard previousCheat.code != code else { return }
- self.delegate.cheatsViewController(self, didDeactivateCheat: previousCheat)
+ self.delegate?.cheatsViewController(self, deactivateCheat: previousCheat)
})
}
}
func editCheatViewController(_ editCheatViewController: EditCheatViewController, deactivateCheat cheat: Cheat)
{
- self.delegate.cheatsViewController(self, didDeactivateCheat: cheat)
+ self.delegate?.cheatsViewController(self, deactivateCheat: cheat)
}
}
diff --git a/Delta/Pause Menu/Cheats/EditCheatViewController.swift b/Delta/Pause Menu/Cheats/EditCheatViewController.swift
index 55ac730..0a9d9f5 100644
--- a/Delta/Pause Menu/Cheats/EditCheatViewController.swift
+++ b/Delta/Pause Menu/Cheats/EditCheatViewController.swift
@@ -14,19 +14,12 @@ import Roxas
protocol EditCheatViewControllerDelegate: class
{
- func editCheatViewController(_ editCheatViewController: EditCheatViewController, activateCheat cheat: Cheat, previousCheat: Cheat?) throws
+ func editCheatViewController(_ editCheatViewController: EditCheatViewController, activateCheat cheat: Cheat, previousCheat: Cheat?)
func editCheatViewController(_ editCheatViewController: EditCheatViewController, deactivateCheat cheat: Cheat)
}
private extension EditCheatViewController
{
- enum ValidationError: ErrorProtocol
- {
- case invalidCode
- case duplicateName
- case duplicateCode
- }
-
enum Section: Int
{
case name
@@ -37,11 +30,18 @@ private extension EditCheatViewController
class EditCheatViewController: UITableViewController
{
- weak var delegate: EditCheatViewControllerDelegate?
+ var game: Game! {
+ didSet {
+ let deltaCore = Delta.core(for: self.game.type)!
+ self.supportedCheatFormats = deltaCore.emulatorConfiguration.supportedCheatFormats
+ }
+ }
var cheat: Cheat?
- var game: Game!
- var supportedCheatFormats: [CheatFormat]!
+
+ weak var delegate: EditCheatViewControllerDelegate?
+
+ private var supportedCheatFormats: [CheatFormat]!
private var selectedCheatFormat: CheatFormat {
let cheatFormat = self.supportedCheatFormats[self.typeSegmentedControl.selectedSegmentIndex]
@@ -271,63 +271,51 @@ private extension EditCheatViewController
do
{
try self.validateCheat(self.mutableCheat)
+
+ self.delegate?.editCheatViewController(self, activateCheat: self.mutableCheat, previousCheat: self.cheat)
+
self.mutableCheat.managedObjectContext?.saveWithErrorLogging()
self.performSegue(withIdentifier: "unwindEditCheatSegue", sender: sender)
}
- catch ValidationError.invalidCode
+ catch CheatValidator.Error.invalidCode
{
self.presentErrorAlert(title: NSLocalizedString("Invalid Code", comment: ""), message: NSLocalizedString("Please make sure you typed the cheat code in correctly and try again.", comment: "")) {
self.codeTextView.becomeFirstResponder()
}
}
- catch ValidationError.duplicateCode
+ catch CheatValidator.Error.invalidName
+ {
+ self.presentErrorAlert(title: NSLocalizedString("Invalid Name", comment: ""), message: NSLocalizedString("Please rename this cheat and try again.", comment: "")) {
+ self.codeTextView.becomeFirstResponder()
+ }
+ }
+ catch CheatValidator.Error.duplicateCode
{
self.presentErrorAlert(title: NSLocalizedString("Duplicate Code", comment: ""), message: NSLocalizedString("A cheat already exists with this code. Please type in a different code and try again.", comment: "")) {
self.codeTextView.becomeFirstResponder()
}
}
- catch ValidationError.duplicateName
+ catch CheatValidator.Error.duplicateName
{
self.presentErrorAlert(title: NSLocalizedString("Duplicate Name", comment: ""), message: NSLocalizedString("A cheat already exists with this name. Please rename this cheat and try again.", comment: "")) {
self.nameTextField.becomeFirstResponder()
}
}
- catch let error as NSError
+ catch
{
print(error)
+
+ self.presentErrorAlert(title: NSLocalizedString("Unknown Error", comment: ""), message: NSLocalizedString("An error occured. Please make sure you typed the cheat code in correctly and try again.", comment: "")) {
+ self.codeTextView.becomeFirstResponder()
+ }
}
}
}
func validateCheat(_ cheat: Cheat) throws
{
- let name = cheat.name!
- let code = cheat.code
-
- // Find all cheats that are for the same game, don't have the same identifier as the current cheat, but have either the same name or code
- let predicate = Predicate(format: "%K == %@ AND %K != %@ AND (%K == %@ OR %K == %@)", Cheat.Attributes.game.rawValue, cheat.game, Cheat.Attributes.identifier.rawValue, cheat.identifier, Cheat.Attributes.code.rawValue, code, Cheat.Attributes.name.rawValue, name)
-
- let cheats = Cheat.instancesWithPredicate(predicate, inManagedObjectContext: self.managedObjectContext, type: Cheat.self)
- for cheat in cheats
- {
- if cheat.name == name
- {
- throw ValidationError.duplicateName
- }
- else if cheat.code == code
- {
- throw ValidationError.duplicateCode
- }
- }
-
- do
- {
- try self.delegate?.editCheatViewController(self, activateCheat: cheat, previousCheat: self.cheat)
- }
- catch
- {
- throw ValidationError.invalidCode
- }
+ let validator = CheatValidator(format: self.selectedCheatFormat, managedObjectContext: self.managedObjectContext)
+ try validator.validate(cheat)
}
@IBAction func textFieldDidEndEditing(_ sender: UITextField)
diff --git a/Delta/Pause Menu/PauseViewController.swift b/Delta/Pause Menu/PauseViewController.swift
index 53602da..c35ea24 100644
--- a/Delta/Pause Menu/PauseViewController.swift
+++ b/Delta/Pause Menu/PauseViewController.swift
@@ -32,10 +32,12 @@ class PauseViewController: UIViewController, PauseInfoProviding
/// PauseInfoProviding
var pauseText: String?
+ /// Cheats
+ weak var cheatsViewControllerDelegate: CheatsViewControllerDelegate?
+
/// Save States
weak var saveStatesViewControllerDelegate: SaveStatesViewControllerDelegate?
- // Hopefully this can be removed once SE-0116 is implemented
private var saveStatesViewControllerMode = SaveStatesViewController.Mode.loading
private var pauseNavigationController: UINavigationController!
@@ -104,7 +106,12 @@ extension PauseViewController
saveStatesViewController.game = self.emulatorCore?.game as? Game
saveStatesViewController.emulatorCore = self.emulatorCore
saveStatesViewController.mode = self.saveStatesViewControllerMode
-
+
+ case "cheats":
+ let cheatsViewController = segue.destinationViewController as! CheatsViewController
+ cheatsViewController.delegate = self.cheatsViewControllerDelegate
+ cheatsViewController.game = self.emulatorCore?.game as? Game
+
default: break
}
}
@@ -150,7 +157,10 @@ private extension PauseViewController
self.performSegue(withIdentifier: "saveStates", sender: self)
})
- self.cheatCodesItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Cheat Codes", comment: ""), action: { _ in })
+ self.cheatCodesItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Cheat Codes", comment: ""), action: { [unowned self] _ in
+ self.performSegue(withIdentifier: "cheats", sender: self)
+ })
+
self.sustainButtonsItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Sustain Buttons", comment: ""), action: { _ in })
self.fastForwardItem = PauseItem(image: UIImage(named: "FastForward")!, text: NSLocalizedString("Fast Forward", comment: ""), action: { _ in })
}