Merge pull request #225 from rileytestut/1.4

Delta 1.4
This commit is contained in:
Riley Testut 2023-02-28 17:35:56 -06:00 committed by GitHub
commit cfa31678ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 9457 additions and 7433 deletions

3
.gitmodules vendored
View File

@ -31,3 +31,6 @@
[submodule "Cores/GPGXDeltaCore"]
path = Cores/GPGXDeltaCore
url = https://github.com/rileytestut/GPGXDeltaCore.git
[submodule "External/CheatBase"]
path = External/CheatBase
url = https://github.com/rileytestut/CheatBase.git

@ -1 +1 @@
Subproject commit e2b3f0e46b4c64670e13fd0466ebdac719f84555
Subproject commit 81362dd5def310f4dd908513574c7e2fd10c7d75

@ -1 +1 @@
Subproject commit 8523e03358559cebaa36b67bd0a87698df238512
Subproject commit c07950a584ed3153b90349059629db85b1c330ac

@ -1 +1 @@
Subproject commit 4313fa6670ab534e70d13532c2504761f849c432
Subproject commit 338a3b32c01413999e390c681dc336fdcdff1ca1

@ -1 +1 @@
Subproject commit ad4ba365acf1800d372ebfaa98df86b9c1b23dce
Subproject commit aed30506c77096c3e854c9a38fe9cc893e9480a1

@ -1 +1 @@
Subproject commit d6b33d89043898d521546d9062d12a505b7d2101
Subproject commit 16f79982e468137c3bfe11a2dbff97423a5ce128

@ -1 +1 @@
Subproject commit e598f512b498e1b639a8d2134113169f4b8d0d26
Subproject commit c581c6f51efd61dc5891fac22dbada7df347c003

@ -1 +1 @@
Subproject commit 78fa7db707655962a1077f4681c35fcf81510060
Subproject commit e5221a06ff2ff830bf60302ef0678fe5e554a925

@ -1 +1 @@
Subproject commit 7539cbaac26a3d2ca9daf47ba22d1b0ebbc41a2b
Subproject commit 2ef4c123b43565688f7b7d3aa5099559b633f7ba

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 52;
objects = {
/* Begin PBXAggregateTarget section */
@ -165,6 +165,20 @@
BFFDF03723E3BB2600931B96 /* libSnes9x.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BFFDF03623E3BB2600931B96 /* libSnes9x.a */; };
BFFDF03F23E3C28A00931B96 /* libGambatte.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BFFDF03D23E3C0F000931B96 /* libGambatte.a */; };
BFFDF04623E3D3A600931B96 /* libMupen64Plus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BFFDF04523E3D3A600931B96 /* libMupen64Plus.a */; };
D524F4A1273DE9A100D500B2 /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = D524F4A0273DE9A100D500B2 /* AltKit */; };
D524F4A3273DE9C000D500B2 /* ProcessInfo+JIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */; };
D524F4A5273DEBB400D500B2 /* ServerManager+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */; };
D5011C48281B6E8B00A0760B /* CharacterSet+Filename.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */; };
D53415A5298C782A00FD67B1 /* ContributorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53415A4298C782A00FD67B1 /* ContributorsView.swift */; };
D53415A7298C78BC00FD67B1 /* Contributors.plist in Resources */ = {isa = PBXBuildFile; fileRef = D53415A6298C78BC00FD67B1 /* Contributors.plist */; };
D5864970297734280081477E /* CheatMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586496F297734280081477E /* CheatMetadata.swift */; };
D586497229774ABD0081477E /* CheatBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586497129774ABD0081477E /* CheatBase.swift */; };
D5864978297756CE0081477E /* CheatBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5864977297756CE0081477E /* CheatBaseView.swift */; };
D5A98CE2284EF14B00E023E5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A98CE1284EF14B00E023E5 /* SceneDelegate.swift */; };
D5AAF27729884F8600F21ACF /* CheatDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AAF27629884F8600F21ACF /* CheatDevice.swift */; };
D5D797E6298D946200738869 /* Contributor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D797E5298D946200738869 /* Contributor.swift */; };
D5D797E9298DCC7300738869 /* cheatbase.zip in Resources */ = {isa = PBXBuildFile; fileRef = D5D797E7298DC9E200738869 /* cheatbase.zip */; };
D5F82FB82981D3AC00B229AF /* LegacySearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F82FB72981D3AC00B229AF /* LegacySearchBar.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -358,6 +372,19 @@
BFFDF03D23E3C0F000931B96 /* libGambatte.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libGambatte.a; sourceTree = BUILT_PRODUCTS_DIR; };
BFFDF04523E3D3A600931B96 /* libMupen64Plus.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libMupen64Plus.a; sourceTree = BUILT_PRODUCTS_DIR; };
C786AF1D2DDB6223BE2063CC /* Pods-Delta.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Delta.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Delta/Pods-Delta.debug.xcconfig"; sourceTree = "<group>"; };
D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+JIT.swift"; sourceTree = "<group>"; };
D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerManager+Delta.swift"; sourceTree = "<group>"; };
D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterSet+Filename.swift"; sourceTree = "<group>"; };
D53415A4298C782A00FD67B1 /* ContributorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributorsView.swift; sourceTree = "<group>"; };
D53415A6298C78BC00FD67B1 /* Contributors.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Contributors.plist; sourceTree = "<group>"; };
D586496F297734280081477E /* CheatMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatMetadata.swift; sourceTree = "<group>"; };
D586497129774ABD0081477E /* CheatBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatBase.swift; sourceTree = "<group>"; };
D5864977297756CE0081477E /* CheatBaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatBaseView.swift; sourceTree = "<group>"; };
D5A98CE1284EF14B00E023E5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
D5AAF27629884F8600F21ACF /* CheatDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatDevice.swift; sourceTree = "<group>"; };
D5D797E5298D946200738869 /* Contributor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contributor.swift; sourceTree = "<group>"; };
D5D797E7298DC9E200738869 /* cheatbase.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = cheatbase.zip; sourceTree = "<group>"; };
D5F82FB72981D3AC00B229AF /* LegacySearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySearchBar.swift; sourceTree = "<group>"; };
DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Delta.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@ -373,6 +400,7 @@
BFDCA1E6244EBAA900B8FBDB /* liblibDeSmuME.a in Frameworks */,
BFCADF1E25D22FE2008D78FB /* Systems.framework in Frameworks */,
BF69FBC923E3A8380051BEEA /* libNestopia.a in Frameworks */,
D524F4A1273DE9A100D500B2 /* AltKit in Frameworks */,
1FA4ABA79AB72914FE414A61 /* libPods-Delta.a in Frameworks */,
BFFDF03F23E3C28A00931B96 /* libGambatte.a in Frameworks */,
BFFDF03723E3BB2600931B96 /* libSnes9x.a in Frameworks */,
@ -403,6 +431,9 @@
BF647A6922FB8FCE0061D76D /* Bundle+SwizzleBundleID.swift */,
BFD1EF3F2336BD8800D197CF /* UIDevice+Processor.swift */,
BFE56E1823EB7BE00014FECD /* UIImage+SymbolFallback.swift */,
D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */,
D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */,
D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -514,6 +545,7 @@
BF59426D1E09BC5D0051894B /* DatabaseManager.swift */,
BF5942711E09BC690051894B /* Model */,
BF95E2751E49763D0030E7AD /* OpenVGDB */,
D586496E297734060081477E /* Cheats */,
);
path = Database;
sourceTree = "<group>";
@ -718,6 +750,7 @@
BF11734E1DA32CEC00047DF8 /* Controllers */,
BF1DAD5B1D9F574900E752A7 /* Controller Skins */,
BF48F74C219A16C100BC2FC1 /* Syncing */,
D5D797E4298D944C00738869 /* Contributors */,
);
path = Settings;
sourceTree = "<group>";
@ -768,6 +801,8 @@
children = (
BF6BB2451BB73FE800CCF94A /* Assets.xcassets */,
BF02D5D91DDEBB3000A5E131 /* openvgdb.sqlite */,
D5D797E7298DC9E200738869 /* cheatbase.zip */,
D53415A6298C78BC00FD67B1 /* Contributors.plist */,
);
path = Resources;
sourceTree = "<group>";
@ -817,6 +852,7 @@
isa = PBXGroup;
children = (
BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */,
D5A98CE1284EF14B00E023E5 /* SceneDelegate.swift */,
BFFA71E01AAC406100EE9DD1 /* Main.storyboard */,
BFFC46211D59848000AF2CC6 /* Launch */,
BF46894D1AAC469800A2586D /* Game Selection */,
@ -877,6 +913,27 @@
path = Launch;
sourceTree = "<group>";
};
D586496E297734060081477E /* Cheats */ = {
isa = PBXGroup;
children = (
D586496F297734280081477E /* CheatMetadata.swift */,
D5AAF27629884F8600F21ACF /* CheatDevice.swift */,
D586497129774ABD0081477E /* CheatBase.swift */,
D5864977297756CE0081477E /* CheatBaseView.swift */,
D5F82FB72981D3AC00B229AF /* LegacySearchBar.swift */,
);
path = Cheats;
sourceTree = "<group>";
};
D5D797E4298D944C00738869 /* Contributors */ = {
isa = PBXGroup;
children = (
D5D797E5298D946200738869 /* Contributor.swift */,
D53415A4298C782A00FD67B1 /* ContributorsView.swift */,
);
path = Contributors;
sourceTree = "<group>";
};
FD1E8AE87FA2DB8793F7B937 /* Pods */ = {
isa = PBXGroup;
children = (
@ -908,6 +965,7 @@
);
name = Delta;
packageProductDependencies = (
D524F4A0273DE9A100D500B2 /* AltKit */,
);
productName = Delta;
productReference = BFFA71D71AAC406100EE9DD1 /* Delta.app */;
@ -957,6 +1015,9 @@
Base,
);
mainGroup = BFFA71CE1AAC406100EE9DD1;
packageReferences = (
D524F49F273DE9A100D500B2 /* XCRemoteSwiftPackageReference "AltKit" */,
);
productRefGroup = BFFA71D81AAC406100EE9DD1 /* Products */;
projectDirPath = "";
projectRoot = "";
@ -973,12 +1034,14 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D5D797E9298DCC7300738869 /* cheatbase.zip in Resources */,
BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */,
BF6BF3211EB82362008E83CD /* GamesDatabase.storyboard in Resources */,
BFAB9F88219A4B670080EC7D /* GoogleService-Info.plist in Resources */,
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */,
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */,
BF02D5DA1DDEBB3000A5E131 /* openvgdb.sqlite in Resources */,
D53415A7298C78BC00FD67B1 /* Contributors.plist in Resources */,
BF71CF8A1FE904B1001F1613 /* GameTableViewCell.xib in Resources */,
BFFC46461D59861000AF2CC6 /* LaunchScreen.storyboard in Resources */,
BF1F45AB21AF4B5800EF9895 /* SyncResultsViewController.storyboard in Resources */,
@ -1114,6 +1177,7 @@
BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */,
BF8A334621A4926F00A42FD4 /* GameSyncStatusViewController.swift in Sources */,
BF59427E1E09BC830051894B /* Game.swift in Sources */,
D5011C48281B6E8B00A0760B /* CharacterSet+Filename.swift in Sources */,
BFE593CC21F3F8C2003412A6 /* _GameSave.swift in Sources */,
BF63A1A321A4AAAE00EE8F61 /* RecordSyncStatusViewController.swift in Sources */,
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
@ -1137,8 +1201,10 @@
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */,
BFC3628021ADE2BA00EF2BE6 /* UIAlertController+Error.swift in Sources */,
BF353FF91C5D870B00C1184C /* MenuItem.swift in Sources */,
D5D797E6298D946200738869 /* Contributor.swift in Sources */,
BFDB3418219E4B1700595A62 /* SyncStatusViewController.swift in Sources */,
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */,
D586497229774ABD0081477E /* CheatBase.swift in Sources */,
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
@ -1146,8 +1212,11 @@
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */,
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */,
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */,
D53415A5298C782A00FD67B1 /* ContributorsView.swift in Sources */,
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
D524F4A5273DEBB400D500B2 /* ServerManager+Delta.swift in Sources */,
D5AAF27729884F8600F21ACF /* CheatDevice.swift in Sources */,
BF1F45AD21AF57BA00EF9895 /* HarmonyMetadataKey+Keys.swift in Sources */,
BFD1EF402336BD8800D197CF /* UIDevice+Processor.swift in Sources */,
BF71CF871FE90006001F1613 /* AppIconShortcutsViewController.swift in Sources */,
@ -1156,6 +1225,7 @@
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */,
BFFA4C091E8A24D600D87934 /* GameTableViewCell.swift in Sources */,
BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */,
D5864970297734280081477E /* CheatMetadata.swift in Sources */,
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */,
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */,
@ -1167,6 +1237,7 @@
BFFC461F1D59823500AF2CC6 /* GamesStoryboardSegue.swift in Sources */,
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */,
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */,
D5864978297756CE0081477E /* CheatBaseView.swift in Sources */,
BF353FFF1C5DA3C500C1184C /* PausePresentationController.swift in Sources */,
BFFC464C1D5998D600AF2CC6 /* CheatTableViewCell.swift in Sources */,
BF5942941E09BD1A0051894B /* NSManagedObject+Conveniences.swift in Sources */,
@ -1175,14 +1246,17 @@
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */,
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */,
D5A98CE2284EF14B00E023E5 /* SceneDelegate.swift in Sources */,
BFE56E1923EB7BE00014FECD /* UIImage+SymbolFallback.swift in Sources */,
BF6EE5E91F7C5F860051AD6C /* _GameControllerInputMapping.swift in Sources */,
BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */,
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
D524F4A3273DE9C000D500B2 /* ProcessInfo+JIT.swift in Sources */,
BFDCA1E9244F7E1000B8FBDB /* Delta5ToDelta6.xcmappingmodel in Sources */,
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */,
BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */,
BF8A333421A484A000A42FD4 /* BadgedTableViewCell.swift in Sources */,
D5F82FB82981D3AC00B229AF /* LegacySearchBar.swift in Sources */,
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */,
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */,
BF3D6C512202865F0083E05A /* Delta2ToDelta3.xcmappingmodel in Sources */,
@ -1407,7 +1481,8 @@
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.Delta;
PRODUCT_NAME = Delta;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
@ -1422,11 +1497,11 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 43;
CURRENT_PROJECT_VERSION = 61;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "Delta/Supporting Files/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.3;
MARKETING_VERSION = 1.4;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -DDEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.Delta;
PROVISIONING_PROFILE = "";
@ -1438,6 +1513,7 @@
SWIFT_OBJC_BRIDGING_HEADER = "Delta/Supporting Files/Delta-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@ -1451,21 +1527,23 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 43;
CURRENT_PROJECT_VERSION = 61;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "Delta/Supporting Files/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.3;
MARKETING_VERSION = 1.4;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -DIMPACTOR";
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.Delta;
PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE_SPECIFIER = "";
STRIP_INSTALLED_PRODUCT = YES;
STRIP_STYLE = "non-global";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_INCLUDE_PATHS = "$(inherited) \"$(SRCROOT)/Cores/GPGXDeltaCore/Sources/GPGXBridge\"";
SWIFT_OBJC_BRIDGING_HEADER = "Delta/Supporting Files/Delta-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@ -1510,6 +1588,25 @@
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
D524F49F273DE9A100D500B2 /* XCRemoteSwiftPackageReference "AltKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/rileytestut/AltKit.git";
requirement = {
kind = revision;
revision = 2fd376df1c79ec06a5c80cc8933da027f65b3148;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
D524F4A0273DE9A100D500B2 /* AltKit */ = {
isa = XCSwiftPackageProductDependency;
package = D524F49F273DE9A100D500B2 /* XCRemoteSwiftPackageReference "AltKit" */;
productName = AltKit;
};
/* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */
BF4828811F9027B600028B97 /* Delta.xcdatamodeld */ = {
isa = XCVersionGroup;

View File

@ -3,7 +3,7 @@
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "NO"
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry

View File

@ -1,12 +1,21 @@
{
"object": {
"pins": [
{
"package": "AltKit",
"repositoryURL": "https://github.com/rileytestut/AltKit.git",
"state": {
"branch": null,
"revision": "2fd376df1c79ec06a5c80cc8933da027f65b3148",
"version": null
}
},
{
"package": "DeltaCore",
"repositoryURL": "https://github.com/rileytestut/DeltaCore.git",
"state": {
"branch": "main",
"revision": "eeefb30bd78fb130f1113e823afbce6f4f767cfb",
"revision": "27a574a46238817084f80d811b3e6c2884d9cdc0",
"version": null
}
},
@ -15,8 +24,8 @@
"repositoryURL": "https://github.com/weichsel/ZIPFoundation.git",
"state": {
"branch": null,
"revision": "ec32d62d412578542c0ffb7a6ce34d3e64b43b94",
"version": "0.9.11"
"revision": "f6a22e7da26314b38bf9befce34ae8e4b2543090",
"version": "0.9.15"
}
}
]

View File

@ -10,6 +10,7 @@ import UIKit
import DeltaCore
import Harmony
import AltKit
import Fabric
import Crashlytics
@ -63,6 +64,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate
// Controllers
ExternalGameControllerManager.shared.startMonitoring()
// JIT
ServerManager.shared.prepare()
// Notifications
let center = CFNotificationCenterGetDarwinNotifyCenter()
CFNotificationCenterAddObserver(center, nil, ReceivedApplicationState, CFNotificationName.altstoreRequestAppState.rawValue, nil, .deliverImmediately)
@ -110,6 +114,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate
}
}
@available(iOS 13, *)
extension AppDelegate
{
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration
{
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Main", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>)
{
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
private extension AppDelegate
{
func registerCores()

View File

@ -46,6 +46,9 @@
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="importButton" destination="FeA-O5-xd2" id="A44-3S-Okz"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="JYx-xE-nis" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18121" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ssH-mM-uG6">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ssH-mM-uG6">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18092"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
@ -279,26 +279,26 @@
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Haptic Feedback" footerTitle="When enabled, your device will vibrate in response to touch screen controls." id="aJ4-xT-aZL">
<tableViewSection headerTitle="Game Audio" footerTitle="When enabled, Delta will only play game audio if your device isn't silenced." id="aJ4-xT-aZL">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="SwitchCell" id="mRV-Bi-pZ8" customClass="SwitchTableViewCell">
<rect key="frame" x="0.0" y="765" width="375" height="44"/>
<rect key="frame" x="0.0" y="794.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="mRV-Bi-pZ8" id="RtM-ok-LGu">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Buttons" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mue-uO-1xE">
<rect key="frame" x="8" y="11.5" width="302" height="21"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Respect Silent Mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mue-uO-1xE">
<rect key="frame" x="16" y="11.5" width="286" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mlO-iy-zU2">
<rect key="frame" x="318" y="6.5" width="51" height="31"/>
<rect key="frame" x="310" y="6.5" width="51" height="31"/>
<color key="onTintColor" name="Purple"/>
<connections>
<action selector="toggleButtonHapticFeedbackEnabled:" destination="eHi-aO-uGS" eventType="primaryActionTriggered" id="au3-JT-77n"/>
<action selector="toggleRespectSilentMode:" destination="eHi-aO-uGS" eventType="primaryActionTriggered" id="dyA-el-uRa"/>
</connections>
</switch>
</subviews>
@ -311,33 +311,67 @@
</constraints>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="SwitchCell" id="2Kn-Xu-sTo" customClass="SwitchTableViewCell">
<rect key="frame" x="0.0" y="809" width="375" height="44"/>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Haptic Feedback" footerTitle="When enabled, your device will vibrate in response to touch screen controls." id="avK-bg-nm6">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="SwitchCell" id="ZUx-5m-gPq" customClass="SwitchTableViewCell">
<rect key="frame" x="0.0" y="930" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="2Kn-Xu-sTo" id="2pb-gv-gY2">
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ZUx-5m-gPq" id="7fY-cO-dht">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Control Sticks" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UoA-qC-cKc">
<rect key="frame" x="8" y="11.5" width="302" height="21"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Buttons" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XPP-qe-q75">
<rect key="frame" x="16" y="11.5" width="286" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="s5f-Uz-GpH">
<rect key="frame" x="318" y="6.5" width="51" height="31"/>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="g7m-wj-ueP">
<rect key="frame" x="310" y="6.5" width="51" height="31"/>
<color key="onTintColor" name="Purple"/>
<connections>
<action selector="toggleThumbstickHapticFeedbackEnabled:" destination="eHi-aO-uGS" eventType="primaryActionTriggered" id="ELm-nK-kTr"/>
<action selector="toggleButtonHapticFeedbackEnabled:" destination="eHi-aO-uGS" eventType="primaryActionTriggered" id="JkH-Ha-NqO"/>
</connections>
</switch>
</subviews>
<constraints>
<constraint firstItem="s5f-Uz-GpH" firstAttribute="centerY" secondItem="2pb-gv-gY2" secondAttribute="centerY" id="1yP-Wt-XbP"/>
<constraint firstAttribute="trailingMargin" secondItem="s5f-Uz-GpH" secondAttribute="trailing" id="3XM-jj-6TV"/>
<constraint firstItem="UoA-qC-cKc" firstAttribute="centerY" secondItem="2pb-gv-gY2" secondAttribute="centerY" id="AMH-D3-llc"/>
<constraint firstItem="UoA-qC-cKc" firstAttribute="leading" secondItem="2pb-gv-gY2" secondAttribute="leadingMargin" id="Fi5-Td-wMi"/>
<constraint firstItem="s5f-Uz-GpH" firstAttribute="leading" secondItem="UoA-qC-cKc" secondAttribute="trailing" constant="8" symbolic="YES" id="L7N-F1-Maf"/>
<constraint firstItem="XPP-qe-q75" firstAttribute="centerY" secondItem="7fY-cO-dht" secondAttribute="centerY" id="ML4-2L-ivB"/>
<constraint firstItem="XPP-qe-q75" firstAttribute="leading" secondItem="7fY-cO-dht" secondAttribute="leadingMargin" id="RcM-9w-cj3"/>
<constraint firstItem="g7m-wj-ueP" firstAttribute="leading" secondItem="XPP-qe-q75" secondAttribute="trailing" constant="8" symbolic="YES" id="U9Z-lS-tZm"/>
<constraint firstAttribute="trailingMargin" secondItem="g7m-wj-ueP" secondAttribute="trailing" id="W92-fx-sPH"/>
<constraint firstItem="g7m-wj-ueP" firstAttribute="centerY" secondItem="7fY-cO-dht" secondAttribute="centerY" id="mKM-Rs-8FG"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="SwitchCell" id="p47-ru-O9r" customClass="SwitchTableViewCell">
<rect key="frame" x="0.0" y="974" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="p47-ru-O9r" id="o9R-cP-Tge">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Control Sticks" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MDR-bb-TYm">
<rect key="frame" x="16" y="11.5" width="286" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="19A-ef-F3V">
<rect key="frame" x="310" y="6.5" width="51" height="31"/>
<color key="onTintColor" name="Purple"/>
<connections>
<action selector="toggleThumbstickHapticFeedbackEnabled:" destination="eHi-aO-uGS" eventType="primaryActionTriggered" id="pHq-hK-bcL"/>
</connections>
</switch>
</subviews>
<constraints>
<constraint firstItem="19A-ef-F3V" firstAttribute="centerY" secondItem="o9R-cP-Tge" secondAttribute="centerY" id="7S9-gQ-Elk"/>
<constraint firstAttribute="trailingMargin" secondItem="19A-ef-F3V" secondAttribute="trailing" id="IVw-V1-wRU"/>
<constraint firstItem="19A-ef-F3V" firstAttribute="leading" secondItem="MDR-bb-TYm" secondAttribute="trailing" constant="8" symbolic="YES" id="hPx-zJ-0qA"/>
<constraint firstItem="MDR-bb-TYm" firstAttribute="centerY" secondItem="o9R-cP-Tge" secondAttribute="centerY" id="m0V-TQ-ajv"/>
<constraint firstItem="MDR-bb-TYm" firstAttribute="leading" secondItem="o9R-cP-Tge" secondAttribute="leadingMargin" id="pSi-wU-tje"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
@ -502,15 +536,38 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Riley Testut" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Pum-dL-hGn">
<rect key="frame" x="16" y="13" width="84" height="19.5"/>
<rect key="frame" x="16" y="12" width="88" height="20.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Developer" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="WQ6-m7-zhh">
<rect key="frame" x="267.5" y="13" width="74" height="19.5"/>
<rect key="frame" x="262.5" y="12" width="78" height="20.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="DetailCell" textLabel="cht-lO-kpR" detailTextLabel="0pG-CT-ZWR" style="IBUITableViewCellStyleValue1" id="CV9-Df-mUX">
<rect key="frame" x="0.0" y="1590.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="CV9-Df-mUX" id="gLC-z2-rMU">
<rect key="frame" x="0.0" y="0.0" width="348.5" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Shane Gill" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="cht-lO-kpR">
<rect key="frame" x="16" y="12" width="76" height="20.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Operations" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="0pG-CT-ZWR">
<rect key="frame" x="256.5" y="12" width="84" height="20.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
@ -527,13 +584,13 @@
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Caroline Moore" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="gWx-Xn-5Nf">
<rect key="frame" x="16" y="13" width="110.5" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Graphic Designer" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="09x-GX-cpy">
<rect key="frame" x="215.5" y="13" width="126" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
@ -550,13 +607,13 @@
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Grant Gliner" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="zro-BX-EY9">
<rect key="frame" x="16" y="13" width="87.5" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Icon Guy" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="e45-FD-ug2">
<rect key="frame" x="277.5" y="13" width="64" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
@ -573,19 +630,35 @@
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Chris Rittenhouse" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="w1i-mR-wOF">
<rect key="frame" x="16" y="13" width="129" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Genesis Skin" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jRO-48-iRO">
<rect key="frame" x="248" y="13" width="93.5" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="DetailCell" textLabel="g59-8E-zW7" style="IBUITableViewCellStyleDefault" id="hkv-lx-68h">
<rect key="frame" x="0.0" y="1766.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="hkv-lx-68h" id="bNT-kB-3cI">
<rect key="frame" x="0.0" y="0.0" width="348.5" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" ambiguous="YES" text="Contributors" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="g59-8E-zW7">
<rect key="frame" x="16" y="0.0" width="324.5" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="DetailCell" textLabel="2K3-IL-94S" style="IBUITableViewCellStyleDefault" id="j7p-ZK-mHq">
<rect key="frame" x="0.0" y="1567.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
@ -621,12 +694,13 @@
</barButtonItem>
</navigationItem>
<connections>
<outlet property="buttonHapticFeedbackEnabledSwitch" destination="mlO-iy-zU2" id="ObD-uL-Si0"/>
<outlet property="buttonHapticFeedbackEnabledSwitch" destination="g7m-wj-ueP" id="nOj-sc-foi"/>
<outlet property="controllerOpacityLabel" destination="zaz-yD-CYG" id="eUW-u9-xxx"/>
<outlet property="controllerOpacitySlider" destination="whi-If-wFf" id="6Cx-HY-xLG"/>
<outlet property="previewsEnabledSwitch" destination="OJE-9e-9i3" id="Ndg-eN-PPs"/>
<outlet property="respectSilentModeSwitch" destination="mlO-iy-zU2" id="TDT-cx-kCf"/>
<outlet property="syncingServiceLabel" destination="kLY-5g-v8n" id="zzx-qM-q1g"/>
<outlet property="thumbstickHapticFeedbackEnabledSwitch" destination="s5f-Uz-GpH" id="ffp-3M-FDL"/>
<outlet property="thumbstickHapticFeedbackEnabledSwitch" destination="19A-ef-F3V" id="K03-kQ-lv3"/>
<outlet property="versionLabel" destination="Str-BY-agW" id="gU2-L0-pYt"/>
<segue destination="uBz-mm-mXr" kind="show" identifier="controllersSegue" id="MLY-hF-UB8"/>
<segue destination="56e-ul-z6v" kind="show" identifier="controllerSkinsSegue" id="GNM-Gt-YFf"/>
@ -1048,6 +1122,7 @@
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="0QR-U9-gtx" customClass="RSTNavigationController" sceneMemberID="viewController">
<toolbarItems/>
<value key="contentSizeForViewInPopover" type="size" width="375" height="667"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" id="Y5H-O6-CQ5">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
@ -1861,10 +1936,48 @@ Delta uses OpenVGDB to provide automatic artwork for imported games.</string>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Performance" footerTitle="When enabled, Delta will automatically enable JIT when on the same WiFi network as AltServer." id="MyM-jI-qtY" userLabel="Performance">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="SwitchCell" id="uXJ-qB-E2e" customClass="SwitchTableViewCell">
<rect key="frame" x="0.0" y="284" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="uXJ-qB-E2e" id="mkz-9w-FlW">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Use AltJIT" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XPc-dG-N6R">
<rect key="frame" x="16" y="11.5" width="286" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4Y4-gY-MaX">
<rect key="frame" x="310" y="6.5" width="51" height="31"/>
<color key="onTintColor" name="Purple"/>
<connections>
<action selector="toggleAltJITEnabled:" destination="OwL-4c-EEA" eventType="valueChanged" id="lfe-xg-J5o"/>
<action selector="togglePreviewsEnabled:" destination="eHi-aO-uGS" eventType="primaryActionTriggered" id="BKJ-dV-k7X"/>
</connections>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="4Y4-gY-MaX" secondAttribute="trailing" id="6Rs-qs-gFM"/>
<constraint firstItem="XPc-dG-N6R" firstAttribute="leading" secondItem="mkz-9w-FlW" secondAttribute="leadingMargin" id="BBM-mW-kez"/>
<constraint firstItem="4Y4-gY-MaX" firstAttribute="centerY" secondItem="mkz-9w-FlW" secondAttribute="centerY" id="CY7-Kt-ggC"/>
<constraint firstItem="4Y4-gY-MaX" firstAttribute="leading" secondItem="XPc-dG-N6R" secondAttribute="trailing" constant="8" symbolic="YES" id="Qce-jc-vX3"/>
<constraint firstItem="XPc-dG-N6R" firstAttribute="centerY" secondItem="mkz-9w-FlW" secondAttribute="centerY" id="Xzp-aR-4Dz"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="switchView" destination="4Y4-gY-MaX" id="NDr-Y9-9RS"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="DS BIOS Files" footerTitle="Delta requires these BIOS files in order to play Nintendo DS games. Tap one to import it from Files." id="ObZ-cl-5sV">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="gray" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="Bfp-iA-wWE" detailTextLabel="Xpe-sd-GRy" style="IBUITableViewCellStyleValue1" id="ian-1X-SOi">
<rect key="frame" x="0.0" y="284" width="375" height="44"/>
<rect key="frame" x="0.0" y="408" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ian-1X-SOi" id="nE9-5Y-G2k">
<rect key="frame" x="0.0" y="0.0" width="349.5" height="44"/>
@ -1887,7 +2000,7 @@ Delta uses OpenVGDB to provide automatic artwork for imported games.</string>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="gray" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="fgD-y0-55T" detailTextLabel="b8s-cx-IgW" style="IBUITableViewCellStyleValue1" id="dwT-0T-gFD">
<rect key="frame" x="0.0" y="328" width="375" height="44"/>
<rect key="frame" x="0.0" y="452" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="dwT-0T-gFD" id="KEg-18-WYZ">
<rect key="frame" x="0.0" y="0.0" width="349.5" height="44"/>
@ -1910,7 +2023,7 @@ Delta uses OpenVGDB to provide automatic artwork for imported games.</string>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="gray" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="SIu-zD-qfE" detailTextLabel="CW8-h0-YST" style="IBUITableViewCellStyleValue1" id="Tll-tX-97T">
<rect key="frame" x="0.0" y="372" width="375" height="44"/>
<rect key="frame" x="0.0" y="496" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Tll-tX-97T" id="4ca-AM-jBE">
<rect key="frame" x="0.0" y="0.0" width="349.5" height="44"/>
@ -1937,7 +2050,7 @@ Delta uses OpenVGDB to provide automatic artwork for imported games.</string>
<tableViewSection headerTitle="DSi BIOS Files" footerTitle="Delta requires these BIOS files in order to play Nintendo DSi games. Tap one to import it from Files." id="SAz-hG-O4G">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="gray" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="QKd-rm-101" detailTextLabel="pJb-OR-TGs" style="IBUITableViewCellStyleValue1" id="jwN-8n-xgM">
<rect key="frame" x="0.0" y="496" width="375" height="44"/>
<rect key="frame" x="0.0" y="620" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="jwN-8n-xgM" id="b29-V1-qt3">
<rect key="frame" x="0.0" y="0.0" width="349.5" height="44"/>
@ -1960,7 +2073,7 @@ Delta uses OpenVGDB to provide automatic artwork for imported games.</string>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="gray" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="6OX-32-izR" detailTextLabel="2bc-8r-epB" style="IBUITableViewCellStyleValue1" id="3ka-mn-QqR">
<rect key="frame" x="0.0" y="540" width="375" height="44"/>
<rect key="frame" x="0.0" y="664" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3ka-mn-QqR" id="4ya-QS-s5w">
<rect key="frame" x="0.0" y="0.0" width="349.5" height="44"/>
@ -1983,7 +2096,7 @@ Delta uses OpenVGDB to provide automatic artwork for imported games.</string>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="gray" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="Czu-qN-Luq" detailTextLabel="eVw-7r-BEf" style="IBUITableViewCellStyleValue1" id="4FF-9a-jq2">
<rect key="frame" x="0.0" y="584" width="375" height="44"/>
<rect key="frame" x="0.0" y="708" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4FF-9a-jq2" id="emb-d3-TgY">
<rect key="frame" x="0.0" y="0.0" width="349.5" height="44"/>
@ -2006,7 +2119,7 @@ Delta uses OpenVGDB to provide automatic artwork for imported games.</string>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="gray" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="VWi-nu-uMv" detailTextLabel="8JD-uT-eoA" style="IBUITableViewCellStyleValue1" id="we3-0h-uKq">
<rect key="frame" x="0.0" y="628" width="375" height="44"/>
<rect key="frame" x="0.0" y="752" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="we3-0h-uKq" id="cG1-a2-Nez">
<rect key="frame" x="0.0" y="0.0" width="349.5" height="44"/>
@ -2033,7 +2146,7 @@ Delta uses OpenVGDB to provide automatic artwork for imported games.</string>
<tableViewSection headerTitle="" footerTitle="Changing cores may improve performance at the cost of additional features." id="hbM-mL-bIr">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="gray" indentationWidth="10" reuseIdentifier="ChangeCell" textLabel="Jmx-Jw-278" style="IBUITableViewCellStyleDefault" id="p1T-4d-fC3">
<rect key="frame" x="0.0" y="737.5" width="375" height="44"/>
<rect key="frame" x="0.0" y="861.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="p1T-4d-fC3" id="3bs-Bp-nd4">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>

View File

@ -21,7 +21,8 @@ class ListMenuViewController: UITableViewController
override var preferredContentSize: CGSize {
get {
let navigationBarHeight = self.navigationController?.navigationBar.bounds.height ?? 0.0
// Don't include navigation bar height in calculation (as of iOS 13).
let navigationBarHeight = 0.0 // self.navigationController?.navigationBar.bounds.height ?? 0.0
return CGSize(width: 0, height: (self.tableView.rowHeight * CGFloat(self.items.count)) + navigationBarHeight)
}
set {}

View File

@ -38,11 +38,23 @@ extension UINavigationBar
private var _defaultTitleTextAttributes: [NSAttributedString.Key: Any]? {
guard self.titleTextAttributes == nil else { return self.titleTextAttributes }
guard
let contentView = self.subviews.first(where: { NSStringFromClass(type(of: $0)).contains("ContentView") || NSStringFromClass(type(of: $0)).contains("ItemView") }),
let titleLabel = contentView.subviews.first(where: { $0 is UILabel }) as? UILabel
guard let contentView = self.subviews.first(where: { NSStringFromClass(type(of: $0)).contains("ContentView") || NSStringFromClass(type(of: $0)).contains("ItemView") })
else { return nil }
let containerView: UIView
if #available(iOS 16, *)
{
guard let titleControl = contentView.subviews.first(where: { NSStringFromClass(type(of: $0)).contains("Title") }) else { return nil }
containerView = titleControl
}
else
{
containerView = contentView
}
guard let titleLabel = containerView.subviews.first(where: { $0 is UILabel }) as? UILabel else { return nil }
let textAttributes = titleLabel.attributedText?.attributes(at: 0, effectiveRange: nil)
return textAttributes
}

View File

@ -0,0 +1,143 @@
//
// CheatBase.swift
// Delta
//
// Created by Riley Testut on 1/17/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import Foundation
import SQLite
import Roxas
private extension UserDefaults
{
@NSManaged var previousCheatBaseVersion: Int
}
extension ExpressionType
{
static var cheatID: SQLite.Expression<Int> {
return SQLite.Expression<Int>("cheatID")
}
static var cheatName: SQLite.Expression<String> {
return SQLite.Expression<String>("cheatName")
}
static var cheatDescription: SQLite.Expression<String?> {
return SQLite.Expression<String?>("cheatDescription")
}
static var cheatCode: SQLite.Expression<String> {
return SQLite.Expression<String>("cheatCode")
}
static var cheatDeviceID: SQLite.Expression<Int> {
return SQLite.Expression<Int>("cheatDeviceID")
}
static var cheatActivation: SQLite.Expression<String?> {
return SQLite.Expression<String?>("cheatActivation")
}
static var cheatCategoryID: SQLite.Expression<Int> {
return SQLite.Expression<Int>("cheatCategoryID")
}
static var cheatCategoryName: SQLite.Expression<String> {
return SQLite.Expression<String>("cheatCategory")
}
static var cheatCategoryDescription: SQLite.Expression<String> {
return SQLite.Expression<String>("cheatCategoryDescription")
}
}
extension Table
{
static var cheats: Table {
return Table("CHEATS")
}
static var cheatCategories: Table {
return Table("CHEAT_CATEGORIES")
}
}
@available(iOS 14, *)
class CheatBase: GamesDatabase
{
static let cheatsVersion = 1
static var previousCheatsVersion: Int? {
return UserDefaults.standard.previousCheatBaseVersion
}
private let connection: Connection
override init() throws
{
let fileURL = DatabaseManager.cheatBaseURL
guard FileManager.default.fileExists(atPath: fileURL.path) else { throw GamesDatabase.Error.doesNotExist }
self.connection = try Connection(fileURL.path)
try super.init()
UserDefaults.standard.previousCheatBaseVersion = CheatBase.cheatsVersion
}
func cheats(for game: Game) async throws -> [CheatMetadata]?
{
let metadata = await withCheckedContinuation { continuation in
if let context = game.managedObjectContext
{
context.perform {
let metadata = self.metadata(for: game)
continuation.resume(returning: metadata)
}
}
else
{
let metadata = self.metadata(for: game)
continuation.resume(returning: metadata)
}
}
guard let romIDValue = metadata?.romID else { return nil }
let cheatID = Expression<Any>.cheatID
let cheatName = Expression<Any>.cheatName
let cheatCode = Expression<Any>.cheatCode
let cheatDescription = Expression<Any>.cheatDescription
let cheatActivation = Expression<Any>.cheatActivation
let cheatDeviceID = Expression<Any>.cheatDeviceID
let categoryID = Expression<Any>.cheatCategoryID
let categoryName = Expression<Any>.cheatCategoryName
let categoryDescription = Expression<Any>.cheatCategoryDescription
let romID = Expression<Any>.romID
let query = Table.cheats.select(cheatID, cheatName, cheatCode, cheatDescription, cheatActivation, cheatDeviceID, Table.cheats[categoryID], categoryName, categoryDescription)
.filter(romID == romIDValue)
.join(Table.cheatCategories, on: Table.cheats[categoryID] == Table.cheatCategories[categoryID])
.order(cheatName)
let rows = try self.connection.prepare(query)
let results = rows.compactMap { (row) -> CheatMetadata? in
guard case let deviceID = Int16(row[cheatDeviceID]), let device = CheatDevice(rawValue: deviceID) else { return nil }
let id = row[Table.cheats[categoryID]]
let category = CheatCategory(id: id, name: row[categoryName], categoryDescription: row[categoryDescription])
let metadata = CheatMetadata(id: row[cheatID], name: row[cheatName], code: row[cheatCode], description: row[cheatDescription], activationHint: row[cheatActivation], device: device, category: category)
return metadata
}
return results
}
}

View File

@ -0,0 +1,273 @@
//
// CheatBaseView.swift
// Delta
//
// Created by Riley Testut on 1/17/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import SwiftUI
import enum SQLite.Result
@available(iOS 14, *)
extension CheatBaseView
{
private class ViewModel: ObservableObject
{
@Published
var allCheats: [CheatMetadata]? {
didSet {
guard let cheats = allCheats else {
self.cheatsByCategory = nil
return
}
let cheatsByCategory = Dictionary(grouping: cheats, by: { $0.category }).sorted { $0.key.id < $1.key.id }
self.cheatsByCategory = cheatsByCategory
}
}
@Published
private(set) var cheatsByCategory: [(CheatCategory, [CheatMetadata])]?
@Published
private(set) var error: Error?
@Published
var searchText: String = "" {
didSet {
self.searchCheats()
}
}
@Published
private(set) var filteredCheats: [CheatMetadata]?
@MainActor
func fetchCheats(for game: Game) async
{
guard self.allCheats == nil else { return }
do
{
let database = try CheatBase()
let cheats = try await database.cheats(for: game) ?? []
self.allCheats = cheats
}
catch
{
self.error = error
}
}
private func searchCheats()
{
if let cheats = self.allCheats, !self.searchText.isEmpty
{
let predicate = NSPredicate(forSearchingForText: self.searchText, inValuesForKeyPaths: [#keyPath(CheatMetadata.name), #keyPath(CheatMetadata.cheatDescription)])
let filteredCheats = cheats.filter { predicate.evaluate(with: $0) }
self.filteredCheats = filteredCheats
}
else
{
self.filteredCheats = nil
}
}
}
}
@available(iOS 14, *)
struct CheatBaseView: View
{
let game: Game?
var cancellationHandler: (() -> Void)?
var selectionHandler: ((CheatMetadata) -> Void)?
@StateObject
private var viewModel = ViewModel()
@State
private var activationHintCheat: CheatMetadata?
var body: some View {
NavigationView {
ZStack {
if let cheats = viewModel.allCheats, !cheats.isEmpty
{
// Only show List if there is at least one cheat for this game.
cheatList()
}
// Place above List
placeholderView()
}
.alert(item: $activationHintCheat) { cheat in
Alert(title: Text("How to Activate"),
message: Text(cheat.activationHint ?? ""),
dismissButton: .default(Text("OK")))
}
.navigationTitle(Text(game?.name ?? "CheatBase"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
cancellationHandler?()
}
}
}
}
.onAppear {
Task {
guard let game = self.game else { return }
await viewModel.fetchCheats(for: game)
}
}
}
private func cheatList() -> some View
{
VStack {
if #unavailable(iOS 15)
{
LegacySearchBar(text: $viewModel.searchText)
}
let listView = List {
if let filteredCheats = viewModel.filteredCheats
{
ForEach(filteredCheats) { cheat in
cell(for: cheat)
}
}
else if let cheats = viewModel.cheatsByCategory
{
ForEach(cheats, id: \.0.id) { (category, cheats) in
Section {
DisclosureGroup {
ForEach(cheats) { cheat in
cell(for: cheat)
}
} label: {
Text(category.name)
}
} footer: {
Text(category.categoryDescription)
}
}
}
}
if #available(iOS 15, *)
{
listView.searchable(text: $viewModel.searchText)
}
else
{
listView
}
}
.listStyle(.insetGrouped)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top)
}
private func cell(for cheat: CheatMetadata) -> some View
{
ZStack(alignment: .leading) {
Button(action: { choose(cheat) }) {}
HStack {
// Name + Description
VStack(alignment: .leading, spacing: 4) {
Text(cheat.name)
if let description = cheat.cheatDescription
{
Text(description)
.font(.caption)
}
}
// Activation Hint
if cheat.activationHint != nil
{
Spacer()
Button(action: { activationHintCheat = cheat }) {
Image(systemName: "info.circle")
}
.buttonStyle(.borderless)
}
}
.multilineTextAlignment(.leading)
}
}
private func placeholderView() -> some View
{
VStack(spacing: 8) {
if let error = viewModel.error
{
Text("Unable to Load Cheats")
.font(.title)
if let error = error as? SQLite.Result
{
// SQLite.Result implements CustomStringConvertible.description, but not localizedDescription.
Text(String(describing: error))
.font(.callout)
}
else
{
Text(error.localizedDescription)
.font(.callout)
}
}
else if let filteredCheats = viewModel.filteredCheats, filteredCheats.isEmpty
{
Text("Cheat Not Found")
.font(.title)
Text("Please make sure the name is correct, or try searching for another cheat.")
.font(.callout)
}
else if let cheats = viewModel.allCheats, cheats.isEmpty
{
Text("No Cheats")
.font(.title)
Text("There are no cheats for this game in Delta's CheatBase. Please try a different game.")
.font(.callout)
}
else if viewModel.allCheats == nil
{
ProgressView()
.progressViewStyle(.circular)
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.foregroundColor(.gray)
.padding()
}
init(game: Game, cheats: [CheatMetadata]? = nil)
{
self.game = game
let viewModel = ViewModel()
viewModel.allCheats = cheats
self._viewModel = StateObject(wrappedValue: viewModel)
}
}
@available(iOS 14, *)
private extension CheatBaseView
{
func choose(_ cheatMetadata: CheatMetadata)
{
self.selectionHandler?(cheatMetadata)
}
}

View File

@ -0,0 +1,111 @@
//
// CheatDevice.swift
// Delta
//
// Created by Riley Testut on 1/30/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import Foundation
import DeltaCore
import NESDeltaCore
@objc
enum CheatDevice: Int16
{
case famicomGameGenie = 1
case famicomRaw = 2
case famicomRawCompare = 3
case gbGameGenie = 4
case gbaActionReplayMax = 5
case gbaCodeBreaker = 6
case gbaGameShark = 7
case gbcGameShark = 8
case n64GameShark = 9
case dsActionReplay = 10
case dsCodeBreaker = 11
case nesGameGenie = 12
case nesRaw = 13
case nesRawCompare = 14
case snesActionReplay = 15
case snesGameGenie = 16
case gameGearActionReplay = 17
case gameGearGameGenie = 18
case masterSystemActionReplay = 19
case masterSystemGameGenie = 20
case cdActionReplay10 = 21
case cdActionReplay8 = 22
case genesisActionReplay10 = 23
case genesisActionReplay8 = 24
}
extension CheatDevice
{
var cheatType: CheatType? {
switch self
{
case .snesActionReplay, .gbaActionReplayMax, .dsActionReplay, .gameGearActionReplay, .masterSystemActionReplay, .genesisActionReplay8, .genesisActionReplay10, .cdActionReplay8, .cdActionReplay10:
return .actionReplay
case .n64GameShark, .gbcGameShark, .gbaGameShark:
return .gameShark
case .famicomGameGenie, .snesGameGenie, .gbGameGenie, .gameGearGameGenie, .masterSystemGameGenie:
return .gameGenie
case .nesGameGenie:
return CheatType(rawValue: DeltaCore.CheatType.gameGenie8.rawValue)
case .gbaCodeBreaker, .dsCodeBreaker:
return .codeBreaker
case .famicomRaw, .famicomRawCompare:
return nil
case .nesRaw, .nesRawCompare:
return nil
}
}
var gameType: GameType? {
switch self
{
case .famicomGameGenie, .famicomRaw, .famicomRawCompare: return .nes
case .nesGameGenie, .nesRaw, .nesRawCompare: return .nes
case .snesActionReplay, .snesGameGenie: return .snes
case .n64GameShark: return .n64
case .gbGameGenie, .gbcGameShark: return .gbc
case .gbaActionReplayMax, .gbaGameShark, .gbaCodeBreaker: return .gba
case .dsActionReplay, .dsCodeBreaker: return .ds
case .genesisActionReplay8, .genesisActionReplay10: return .genesis
case .cdActionReplay8, .cdActionReplay10: return .genesis
// Not yet supported
case .gameGearActionReplay, .gameGearGameGenie: return nil
case .masterSystemActionReplay, .masterSystemGameGenie: return nil
}
}
var cheatFormat: CheatFormat? {
guard
let cheatType = self.cheatType,
let gameType = self.gameType,
let deltaCore = Delta.core(for: gameType)
else { return nil }
let cheatFormat = deltaCore.supportedCheatFormats.first { $0.type == cheatType }
return cheatFormat
}
}

View File

@ -0,0 +1,45 @@
//
// CheatMetadata.swift
// Delta
//
// Created by Riley Testut on 1/17/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import UIKit
import DeltaCore
struct CheatCategory: Identifiable, Hashable
{
var id: Int
var name: String
var categoryDescription: String
}
@objcMembers // @objcMembers required for NSPredicate-based filtering.
final class CheatMetadata: NSObject, Identifiable
{
let id: Int
let name: String
let code: String
let cheatDescription: String?
let activationHint: String?
let device: CheatDevice
let category: CheatCategory
init(id: Int, name: String, code: String, description: String?, activationHint: String?, device: CheatDevice, category: CheatCategory)
{
self.id = id
self.name = name
self.code = code
self.cheatDescription = description
self.activationHint = activationHint
self.device = device
self.category = category
}
}

View File

@ -0,0 +1,51 @@
//
// LegacySearchBar.swift
// Delta
//
// Created by Riley Testut on 1/25/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import UIKit
import SwiftUI
@available(iOS 13, *)
struct LegacySearchBar: UIViewRepresentable
{
class Coordinator: NSObject, UISearchBarDelegate
{
@Binding
var text: String
init(text: Binding<String>)
{
self._text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String)
{
self.text = searchText
}
}
@Binding
var text: String
func makeUIView(context: Context) -> UISearchBar
{
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
searchBar.placeholder = NSLocalizedString("Search", comment: "")
return searchBar
}
func updateUIView(_ uiView: UISearchBar, context: Context)
{
uiView.text = self.text
}
func makeCoordinator() -> Coordinator
{
return Coordinator(text: $text)
}
}

View File

@ -255,10 +255,27 @@ private extension DatabaseManager
do
{
if !FileManager.default.fileExists(atPath: DatabaseManager.gamesDatabaseURL.path)
if !FileManager.default.fileExists(atPath: DatabaseManager.gamesDatabaseURL.path) || GamesDatabase.version != GamesDatabase.previousVersion
{
guard let bundleURL = Bundle.main.url(forResource: "openvgdb", withExtension: "sqlite") else { throw GamesDatabase.Error.doesNotExist }
try FileManager.default.copyItem(at: bundleURL, to: DatabaseManager.gamesDatabaseURL)
try FileManager.default.copyItem(at: bundleURL, to: DatabaseManager.gamesDatabaseURL, shouldReplace: true)
}
if #available(iOS 14, *), !FileManager.default.fileExists(atPath: DatabaseManager.cheatBaseURL.path) || CheatBase.cheatsVersion != CheatBase.previousCheatsVersion
{
guard let archiveURL = Bundle.main.url(forResource: "cheatbase", withExtension: "zip") else { throw GamesDatabase.Error.doesNotExist }
let temporaryDirectoryURL = FileManager.default.uniqueTemporaryURL()
try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true)
defer {
try? FileManager.default.removeItem(at: temporaryDirectoryURL)
}
// Unzip to temporaryDirectoryURL first to ensure we don't accidentally unzip other items into DatabaseManager.cheatBaseURL directory (e.g. __MACOSX directory).
try FileManager.default.unzipItem(at: archiveURL, to: temporaryDirectoryURL, skipCRC32: true) // skipCRC32 to avoid ~10 second extraction.
let extractedDatabaseURL = temporaryDirectoryURL.appendingPathComponent("cheatbase.sqlite")
try FileManager.default.copyItem(at: extractedDatabaseURL, to: DatabaseManager.cheatBaseURL, shouldReplace: true)
}
self.gamesDatabase = try GamesDatabase()
@ -618,6 +635,12 @@ extension DatabaseManager
let gamesDatabaseURL = self.defaultDirectoryURL().appendingPathComponent("openvgdb.sqlite")
return gamesDatabaseURL
}
class var cheatBaseURL: URL
{
let gamesDatabaseURL = self.defaultDirectoryURL().appendingPathComponent("cheatbase.sqlite")
return gamesDatabaseURL
}
class var gamesDirectoryURL: URL
{

View File

@ -71,11 +71,6 @@ extension ControllerSkin: ControllerSkinProtocol
return self.controllerSkin?.thumbstick(for: item, traits: traits, preferredSize: preferredSize)
}
public func inputs(for traits: DeltaCore.ControllerSkin.Traits, at point: CGPoint) -> [Input]?
{
return self.controllerSkin?.inputs(for: traits, at: point)
}
public func items(for traits: DeltaCore.ControllerSkin.Traits) -> [DeltaCore.ControllerSkin.Item]?
{
return self.controllerSkin?.items(for: traits)

View File

@ -45,12 +45,16 @@ public class Game: _Game, GameProtocol
// Recreate the stored URL relative to current sandbox location.
artworkURL = URL(fileURLWithPath: unwrappedArtworkURL.relativePath, relativeTo: DatabaseManager.gamesDirectoryURL)
}
else if unwrappedArtworkURL.host?.lowercased() == "img.gamefaqs.net", var components = URLComponents(url: unwrappedArtworkURL, resolvingAgainstBaseURL: false)
else if let host = unwrappedArtworkURL.host?.lowercased(), host == "img.gamefaqs.net" || host == "gamefaqs1.cbsistatic.com",
var components = URLComponents(url: unwrappedArtworkURL, resolvingAgainstBaseURL: false)
{
// Quick fix for broken album artwork URLs due to host change.
components.host = "gamefaqs1.cbsistatic.com"
components.host = "gamefaqs.gamespot.com"
components.scheme = "https"
let updatedPath = "/a" + components.path
components.path = updatedPath
if let url = components.url
{
artworkURL = url

View File

@ -11,15 +11,17 @@ import Foundation
// Must be an NSObject subclass so it can be used with RSTCellContentDataSource.
class GameMetadata: NSObject
{
let identifier: Int
let releaseID: Int
let romID: Int
let name: String?
let artworkURL: URL?
init(identifier: Int, name: String?, artworkURL: URL?)
init(releaseID: Int, romID: Int, name: String?, artworkURL: URL?)
{
self.releaseID = releaseID
self.romID = romID
self.name = name
self.identifier = identifier
self.artworkURL = artworkURL
}
}
@ -27,13 +29,13 @@ class GameMetadata: NSObject
extension GameMetadata
{
override var hash: Int {
return self.identifier.hashValue
return self.releaseID.hashValue ^ self.romID.hashValue
}
override func isEqual(_ object: Any?) -> Bool
{
guard let metadata = object as? GameMetadata else { return false }
return self.identifier == metadata.identifier
return self.releaseID == metadata.releaseID && self.romID == metadata.romID
}
}

View File

@ -57,16 +57,26 @@ extension VirtualTable
extension GamesDatabase
{
enum Error: Swift.Error
enum Error: LocalizedError
{
case doesNotExist
case connection(Swift.Error)
var errorDescription: String? {
switch self
{
case .doesNotExist:
return NSLocalizedString("The SQLite database could not be found.", comment: "")
}
}
}
}
class GamesDatabase
{
static let version = -1
static let version = 3
static var previousVersion: Int? {
return UserDefaults.standard.previousGamesDatabaseVersion
}
private let connection: Connection
@ -80,7 +90,7 @@ class GamesDatabase
}
catch
{
throw Error.connection(error)
throw error
}
self.invalidateVirtualTableIfNeeded()
@ -89,10 +99,11 @@ class GamesDatabase
func metadataResults(forGameName gameName: String) -> [GameMetadata]
{
let releaseID = Expression<Any>.releaseID
let romID = Expression<Any>.romID
let name = Expression<Any>.name
let artworkAddress = Expression<Any>.artworkAddress
let query = VirtualTable.search.select(releaseID, name, artworkAddress).filter(name.match(gameName + "*"))
let query = VirtualTable.search.select(releaseID, romID, name, artworkAddress).filter(name.match(gameName + "*"))
do
{
@ -111,7 +122,7 @@ class GamesDatabase
}
let metadata = GameMetadata(identifier: row[releaseID], name: row[name], artworkURL: artworkURL)
let metadata = GameMetadata(releaseID: row[releaseID], romID: row[romID], name: row[name], artworkURL: artworkURL)
return metadata
}
@ -145,7 +156,7 @@ class GamesDatabase
let romID = Expression<Any>.romID
let gameHash = game.identifier.uppercased()
let query = Table.roms.select(releaseID, name, artworkAddress).filter(sha1Hash == gameHash).join(Table.releases, on: Table.roms[romID] == Table.releases[romID])
let query = Table.roms.select(releaseID, name, artworkAddress, Table.roms[romID]).filter(sha1Hash == gameHash).join(Table.releases, on: Table.roms[romID] == Table.releases[romID])
do
{
@ -161,7 +172,7 @@ class GamesDatabase
artworkURL = nil
}
let metadata = GameMetadata(identifier: row[releaseID], name: row[name], artworkURL: artworkURL)
let metadata = GameMetadata(releaseID: row[releaseID], romID: row[Table.roms[romID]], name: row[name], artworkURL: artworkURL)
return metadata
}
}
@ -197,12 +208,13 @@ private extension GamesDatabase
let name = Expression<Any>.name
let artworkAddress = Expression<Any>.artworkAddress
let releaseID = Expression<Any>.releaseID
let romID = Expression<Any>.romID
do
{
try self.connection.run(VirtualTable.search.create(.FTS4([releaseID, name, artworkAddress], tokenize: .Unicode61())))
try self.connection.run(VirtualTable.search.create(.FTS4([releaseID, romID, name, artworkAddress], tokenize: .Unicode61())))
let update = VirtualTable.search.insert(Table.releases.select(releaseID, name, artworkAddress))
let update = VirtualTable.search.insert(Table.releases.select(releaseID, romID, name, artworkAddress))
_ = try self.connection.run(update)
}
catch

View File

@ -15,6 +15,8 @@ extension UIActivity.ActivityType
class CopyDeepLinkActivity: UIActivity
{
private var deepLink: URL?
override class var activityCategory: UIActivity.Category {
return .action
}
@ -28,7 +30,7 @@ class CopyDeepLinkActivity: UIActivity
}
override var activityImage: UIImage? {
return UIImage(named: "Link")
return UIImage(symbolNameIfAvailable: "link") ?? UIImage(named: "Link")
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool
@ -47,7 +49,19 @@ class CopyDeepLinkActivity: UIActivity
{
guard let game = activityItems.first(where: { $0 is Game }) as? Game else { return }
let deepLink = URL(action: .launchGame(identifier: game.identifier))
UIPasteboard.general.url = deepLink
self.deepLink = URL(action: .launchGame(identifier: game.identifier))
}
override func perform()
{
if let deepLink = self.deepLink
{
UIPasteboard.general.url = deepLink
self.activityDidFinish(true)
}
else
{
self.activityDidFinish(false)
}
}
}

View File

@ -23,8 +23,16 @@ extension UIViewController
struct DeepLinkController
{
private var window: UIWindow? {
guard let delegate = UIApplication.shared.delegate, let window = delegate.window else { return nil }
return window
if #available(iOS 13, *)
{
guard let delegate = UIApplication.shared.connectedScenes.lazy.compactMap({ $0.delegate as? UIWindowSceneDelegate }).first, let window = delegate.window else { return nil }
return window
}
else
{
guard let delegate = UIApplication.shared.delegate, let window = delegate.window else { return nil }
return window
}
}
private var topViewController: UIViewController? {

View File

@ -10,11 +10,13 @@ import UIKit
import DeltaCore
import GBADeltaCore
import MelonDSDeltaCore
import Systems
import struct DSDeltaCore.DS
import Roxas
import AltKit
private var kvoContext = 0
@ -110,6 +112,7 @@ class GameViewController: DeltaCore.GameViewController
}
self.updateControllers()
self.updateAudio()
self.presentedGyroAlert = false
}
@ -167,6 +170,8 @@ class GameViewController: DeltaCore.GameViewController
private var isGyroActive = false
private var presentedGyroAlert = false
private var presentedJITAlert = false
override var shouldAutorotate: Bool {
return !self.isGyroActive
}
@ -205,6 +210,8 @@ class GameViewController: DeltaCore.GameViewController
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.didDeactivateGyro(with:)), name: GBA.didDeactivateGyroNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.emulationDidQuit(with:)), name: EmulatorCore.emulationDidQuitNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.didEnableJIT(with:)), name: ServerManager.didEnableJITNotification, object: nil)
}
deinit
@ -330,12 +337,18 @@ extension GameViewController
UserDefaults.standard.desmumeDeprecatedAlertCount += 1
}
else if self.emulatorCore?.deltaCore == MelonDS.core, ProcessInfo.processInfo.isJITAvailable
{
self.showJITEnabledAlert()
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{
super.viewWillTransition(to: size, with: coordinator)
guard UIApplication.shared.applicationState != .background else { return }
coordinator.animate(alongsideTransition: { (context) in
self.updateControllerSkin()
}, completion: nil)
@ -474,6 +487,13 @@ extension GameViewController
}
self._isLoadingSaveState = false
if self.emulatorCore?.deltaCore == MelonDS.core, ProcessInfo.processInfo.isJITAvailable
{
self.transitionCoordinator?.animate(alongsideTransition: nil, completion: { (context) in
self.showJITEnabledAlert()
})
}
}
case "unwindToGames":
@ -646,12 +666,14 @@ private extension GameViewController
else if let controllerSkin = DeltaCore.ControllerSkin.standardControllerSkin(for: game.type), controllerSkin.hasTouchScreen(for: traits)
{
var touchControllerSkin = TouchControllerSkin(controllerSkin: controllerSkin)
touchControllerSkin.layoutGuide = self.view.safeAreaLayoutGuide
switch traits.orientation
if self.view.bounds.width > self.view.bounds.height
{
case .portrait: touchControllerSkin.screenLayoutAxis = .vertical
case .landscape: touchControllerSkin.screenLayoutAxis = .horizontal
touchControllerSkin.screenLayoutAxis = .horizontal
}
else
{
touchControllerSkin.screenLayoutAxis = .vertical
}
self.controllerView.controllerSkin = touchControllerSkin
@ -914,6 +936,16 @@ extension GameViewController: CheatsViewControllerDelegate
}
}
//MARK: - Audio -
/// Audio
private extension GameViewController
{
func updateAudio()
{
self.emulatorCore?.audioManager.respectsSilentMode = Settings.respectSilentMode
}
}
//MARK: - Sustain Buttons -
private extension GameViewController
{
@ -934,6 +966,8 @@ private extension GameViewController
UIView.animate(withDuration: 0.4) {
self.sustainButtonsBlurView.effect = blurEffect
self.sustainButtonsBackgroundView.alpha = 1.0
} completion: { _ in
self.controllerView.becomeFirstResponder()
}
}
@ -1061,6 +1095,8 @@ extension GameViewController: GameViewControllerDelegate
else if self.presentedViewController == nil
{
self.pauseEmulation()
self.controllerView.resignFirstResponder()
self.performSegue(withIdentifier: "pause", sender: gameController)
}
}
@ -1085,6 +1121,48 @@ private extension GameViewController
toastView.presentationEdge = .top
toastView.show(in: self.view, duration: duration)
}
func showJITEnabledAlert()
{
guard !self.presentedJITAlert, self.presentedViewController == nil, self.game != nil else { return }
self.presentedJITAlert = true
func presentToastView()
{
let detailText: String?
let duration: TimeInterval
if UserDefaults.standard.jitEnabledAlertCount < 3
{
detailText = NSLocalizedString("You can now Fast Forward DS games up to 3x speed.", comment: "")
duration = 5.0
}
else
{
detailText = nil
duration = 2.0
}
let toastView = RSTToastView(text: NSLocalizedString("JIT Compilation Enabled", comment: ""), detailText: detailText)
toastView.edgeOffset.vertical = 8
self.show(toastView, duration: duration)
UserDefaults.standard.jitEnabledAlertCount += 1
}
DispatchQueue.main.async {
if let transitionCoordinator = self.transitionCoordinator
{
transitionCoordinator.animate(alongsideTransition: nil) { (context) in
presentToastView()
}
}
else
{
presentToastView()
}
}
}
}
//MARK: - Notifications -
@ -1127,9 +1205,13 @@ private extension GameViewController
self.updateControllerSkin()
}
case .translucentControllerSkinOpacity: self.controllerView.translucentControllerSkinOpacity = Settings.translucentControllerSkinOpacity
case .translucentControllerSkinOpacity:
self.controllerView.translucentControllerSkinOpacity = Settings.translucentControllerSkinOpacity
case .syncingService: break
case .respectSilentMode:
self.updateAudio()
case .syncingService, .isAltJITEnabled: break
}
}
@ -1207,6 +1289,60 @@ private extension GameViewController
self.isGyroActive = false
}
@objc func didEnableJIT(with notification: Notification)
{
DispatchQueue.main.async {
self.showJITEnabledAlert()
}
DispatchQueue.global(qos: .utility).async {
guard let emulatorCore = self.emulatorCore, let emulatorBridge = emulatorCore.deltaCore.emulatorBridge as? MelonDSEmulatorBridge, !emulatorBridge.isJITEnabled
else { return }
guard emulatorCore.state != .stopped else {
// Emulator core is not running, which means we can set
// isJITEnabled to true without resetting the core.
emulatorBridge.isJITEnabled = true
return
}
let isVideoEnabled = emulatorCore.videoManager.isEnabled
emulatorCore.videoManager.isEnabled = false
let isRunning = (emulatorCore.state == .running)
if isRunning
{
self.pauseEmulation()
}
let temporaryFileURL = FileManager.default.uniqueTemporaryURL()
let saveState = emulatorCore.saveSaveState(to: temporaryFileURL)
emulatorCore.stop()
emulatorBridge.isJITEnabled = true
emulatorCore.start()
emulatorCore.pause()
do
{
try emulatorCore.load(saveState)
}
catch
{
print("Failed to load save state after enabling JIT.", error)
}
if isRunning
{
self.resumeEmulation()
}
emulatorCore.videoManager.isEnabled = isVideoEnabled
}
}
@objc func emulationDidQuit(with notification: Notification)
{
DispatchQueue.main.async {
@ -1231,4 +1367,6 @@ private extension GameViewController
private extension UserDefaults
{
@NSManaged var desmumeDeprecatedAlertCount: Int
@NSManaged var jitEnabledAlertCount: Int
}

View File

@ -0,0 +1,22 @@
//
// CharacterSet+Filename.swift
// Delta
//
// Created by Riley Testut on 4/28/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import Foundation
extension CharacterSet
{
// Different than .urlPathAllowed
// Copied from https://stackoverflow.com/a/39443252
static var urlFilenameAllowed: CharacterSet {
var illegalCharacters = CharacterSet(charactersIn: ":/")
illegalCharacters.formUnion(.newlines)
illegalCharacters.formUnion(.illegalCharacters)
illegalCharacters.formUnion(.controlCharacters)
return illegalCharacters.inverted
}
}

View File

@ -0,0 +1,41 @@
//
// ProcessInfo+JIT.swift
// Delta
//
// Created by Riley Testut on 9/14/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import UIKit
private let CS_OPS_STATUS: UInt32 = 0 /* OK */
private let CS_DEBUGGED: UInt32 = 0x10000000 /* Process is or has been attached to debugger. */
@_silgen_name("csops")
func csops(_ pid: pid_t, _ ops: UInt32, _ useraddr: UnsafeMutableRawPointer?, _ usersize: Int) -> Int
extension ProcessInfo
{
static var isJITDisabled = false
var isDebugging: Bool {
var flags: UInt32 = 0
let result = csops(getpid(), CS_OPS_STATUS, &flags, MemoryLayout<UInt32>.size)
let isDebugging = result == 0 && (flags & CS_DEBUGGED == CS_DEBUGGED)
return isDebugging
}
var isJITAvailable: Bool {
guard UIDevice.current.supportsJIT && !ProcessInfo.isJITDisabled else { return false }
let ios14_4 = OperatingSystemVersion(majorVersion: 14, minorVersion: 4, patchVersion: 0)
if #available(iOS 14.2, *), !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14_4)
{
// JIT is always available on supported devices running iOS 14.2 - 14.3.
return true
}
return self.isDebugging
}
}

View File

@ -0,0 +1,104 @@
//
// ServerManager+Delta.swift
// Delta
//
// Created by Riley Testut on 9/15/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import AltKit
extension ServerManager
{
static let didEnableJITNotification = Notification.Name("didEnableJITNotification")
}
extension ServerManager
{
func prepare()
{
NotificationCenter.default.addObserver(self, selector: #selector(ServerManager.didChangeJITMode(_:)), name: .settingsDidChange, object: nil)
#if DEBUG
if ProcessInfo.processInfo.isDebugging
{
// Debugger is attached at app launch, so we assume
// we're connected to Xcode for debugging purposes.
// In that case, we manually treat JIT as unavailable
// until AltServer is discovered to simulate real-world use.
ProcessInfo.isJITDisabled = true
}
#endif
self.start()
}
}
private extension ServerManager
{
func start()
{
guard Settings.isAltJITEnabled && !ProcessInfo.processInfo.isJITAvailable else { return }
self.startDiscovering()
self.autoconnect()
}
func autoconnect()
{
self.autoconnect { result in
switch result
{
case .failure(let error):
print("Could not auto-connect to server.", error)
self.autoconnect()
case .success(let connection):
func finish(result: Result<Void, Error>)
{
switch result
{
case .failure(ALTServerError.unknownRequest), .failure(ALTServerError.deviceNotFound):
// Try connecting to a different server.
self.autoconnect()
case .failure(let error):
print("Could not enable JIT compilation.", error)
case .success:
print("Successfully enabled JIT compilation!")
NotificationCenter.default.post(name: ServerManager.didEnableJITNotification, object: nil)
self.stopDiscovering()
}
connection.disconnect()
}
if ProcessInfo.isJITDisabled
{
ProcessInfo.isJITDisabled = false
finish(result: .success(()))
}
else
{
connection.enableUnsignedCodeExecution(completion: finish)
}
}
}
}
@objc func didChangeJITMode(_ notification: Notification)
{
guard let name = notification.userInfo?[Settings.NotificationUserInfoKey.name] as? Settings.Name, name == Settings.Name.isAltJITEnabled else { return }
if Settings.isAltJITEnabled
{
self.start()
}
else
{
self.stopDiscovering()
}
}
}

View File

@ -26,12 +26,9 @@ extension UIDevice
}
var supportsJIT: Bool {
// As of iOS 14.4 beta 2, JIT is no longer supported :(
// Hopefully this change is reversed before the public release...
let ios14_4 = OperatingSystemVersion(majorVersion: 14, minorVersion: 4, patchVersion: 0)
guard #available(iOS 14.2, *), !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14_4) else { return false }
guard #available(iOS 14.0, *) else { return false }
// JIT is supported on devices with an A12 processor or better running iOS 14.2 or later.
// JIT is supported on devices with an A12 processor or better running iOS 14.0 or later.
// ARKit 3 is only supported by devices with an A12 processor or better, according to the documentation.
return ARBodyTrackingConfiguration.isSupported
}

View File

@ -66,6 +66,8 @@ class GameCollectionViewController: UICollectionViewController
private weak var _previewTransitionViewController: PreviewGameViewController?
private weak var _previewTransitionDestinationViewController: UIViewController?
private weak var _popoverSourceView: UIView?
private var _renameAction: UIAlertAction?
private var _changingArtworkGame: Game?
private var _importingSaveFileGame: Game?
@ -93,10 +95,6 @@ extension GameCollectionViewController
self.collectionView?.prefetchDataSource = self.dataSource
self.collectionView?.delegate = self
let layout = self.collectionViewLayout as! GridCollectionViewLayout
layout.itemWidth = 90
layout.minimumInteritemSpacing = 12
if #available(iOS 13, *) {}
else
{
@ -105,6 +103,8 @@ extension GameCollectionViewController
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(GameCollectionViewController.handleLongPressGesture(_:)))
self.collectionView?.addGestureRecognizer(longPressGestureRecognizer)
}
self.update()
}
override func viewWillDisappear(_ animated: Bool)
@ -131,6 +131,13 @@ extension GameCollectionViewController
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
super.traitCollectionDidChange(previousTraitCollection)
self.update()
}
}
//MARK: - Segues -
@ -180,7 +187,7 @@ extension GameCollectionViewController
emulatorBridge.systemType = .ds
}
emulatorBridge.isJITEnabled = UIDevice.current.supportsJIT
emulatorBridge.isJITEnabled = ProcessInfo.processInfo.isJITAvailable
}
if let saveState = self.activeSaveState
@ -224,6 +231,26 @@ extension GameCollectionViewController
//MARK: - Private Methods -
private extension GameCollectionViewController
{
func update()
{
let layout = self.collectionViewLayout as! GridCollectionViewLayout
switch self.traitCollection.horizontalSizeClass
{
case .regular:
layout.itemWidth = 150
layout.minimumInteritemSpacing = 25 // 30 == only 3 games per line for iPad mini 6 in portrait
case .unspecified, .compact:
layout.itemWidth = 90
layout.minimumInteritemSpacing = 12
@unknown default: break
}
self.collectionView.reloadData()
}
//MARK: - Data Source
func prepareDataSource()
{
@ -284,7 +311,19 @@ private extension GameCollectionViewController
cell.imageView.image = #imageLiteral(resourceName: "BoxArt")
cell.maximumImageSize = CGSize(width: 90, height: 90)
if self.traitCollection.horizontalSizeClass == .regular
{
let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline).withSymbolicTraits(.traitBold)!
cell.textLabel.font = UIFont(descriptor: fontDescriptor, size: 0)
}
else
{
cell.textLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
}
let layout = self.collectionViewLayout as! GridCollectionViewLayout
cell.maximumImageSize = CGSize(width: layout.itemWidth, height: layout.itemWidth)
cell.textLabel.text = game.name
cell.textLabel.textColor = UIColor.gray
cell.tintColor = cell.textLabel.textColor
@ -484,7 +523,9 @@ private extension GameCollectionViewController
func delete(_ game: Game)
{
let confirmationAlertController = UIAlertController(title: NSLocalizedString("Are you sure you want to delete this game? All associated data, such as saves, save states, and cheat codes, will also be deleted.", comment: ""), message: nil, preferredStyle: .actionSheet)
let confirmationAlertController = UIAlertController(title: NSLocalizedString("Are you sure you want to delete this game?", comment: ""),
message: NSLocalizedString("All associated data, such as saves, save states, and cheat codes, will also be deleted.", comment: ""),
preferredStyle: .alert)
confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Delete Game", comment: ""), style: .destructive, handler: { action in
DatabaseManager.shared.performBackgroundTask { (context) in
@ -554,6 +595,7 @@ private extension GameCollectionViewController
let importController = ImportController(documentTypes: [kUTTypeImage as String])
importController.delegate = self
importController.importOptions = [clipboardImportOption, photoLibraryImportOption, gamesDatabaseImportOption]
importController.sourceView = self._popoverSourceView
self.present(importController, animated: true, completion: nil)
}
@ -663,26 +705,36 @@ private extension GameCollectionViewController
func share(_ game: Game)
{
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
let symbolicURL = temporaryDirectory.appendingPathComponent(game.name + "." + game.fileURL.pathExtension)
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let sanitizedName = game.name.components(separatedBy: .urlFilenameAllowed.inverted).joined()
let temporaryURL = temporaryDirectory.appendingPathComponent(sanitizedName + "." + game.fileURL.pathExtension, isDirectory: false)
do
{
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
// Create a symbolic link so we can control the file name used when sharing.
// Make a temporary copy so we can control the filename used when sharing.
// Otherwise, if we just passed in game.fileURL to UIActivityViewController, the file name would be the game's SHA1 hash.
try FileManager.default.createSymbolicLink(at: symbolicURL, withDestinationURL: game.fileURL)
try FileManager.default.copyItem(at: game.fileURL, to: temporaryURL, shouldReplace: true)
}
catch
{
print(error)
let alertController = UIAlertController(title: NSLocalizedString("Could Not Share Game", comment: ""), error: error)
self.present(alertController, animated: true, completion: nil)
return
}
let copyDeepLinkActivity = CopyDeepLinkActivity()
let activityViewController = UIActivityViewController(activityItems: [symbolicURL, game], applicationActivities: [copyDeepLinkActivity])
let activityViewController = UIActivityViewController(activityItems: [temporaryURL, game], applicationActivities: [copyDeepLinkActivity])
activityViewController.popoverPresentationController?.sourceView = self._popoverSourceView?.superview
activityViewController.popoverPresentationController?.sourceRect = self._popoverSourceView?.frame ?? .zero
activityViewController.completionWithItemsHandler = { (activityType, finished, returnedItems, error) in
// Make sure the user either shared the game or cancelled before deleting temporaryDirectory.
guard finished || activityType == nil else { return }
do
{
try FileManager.default.removeItem(at: temporaryDirectory)
@ -692,6 +744,7 @@ private extension GameCollectionViewController
print(error)
}
}
self.present(activityViewController, animated: true, completion: nil)
}
@ -747,8 +800,7 @@ private extension GameCollectionViewController
{
do
{
let illegalCharacterSet = CharacterSet(charactersIn: "\"\\/?<>:*|")
let sanitizedFilename = game.name.components(separatedBy: illegalCharacterSet).joined() + "." + game.gameSaveURL.pathExtension
let sanitizedFilename = game.name.components(separatedBy: .urlFilenameAllowed.inverted).joined()
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(sanitizedFilename)
try FileManager.default.copyItem(at: game.gameSaveURL, to: temporaryURL, shouldReplace: true)
@ -807,6 +859,9 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
previewingContext.sourceRect = layoutAttributes.frame
let cell = collectionView.cellForItem(at: indexPath)
self._popoverSourceView = cell
let game = self.dataSource.item(at: indexPath)
let gameViewController = self.makePreviewGameViewController(for: game)
@ -843,7 +898,7 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
emulatorBridge.systemType = .ds
}
emulatorBridge.isJITEnabled = UIDevice.current.supportsJIT
emulatorBridge.isJITEnabled = ProcessInfo.processInfo.isJITAvailable
}
let actions = self.actions(for: game).previewActions
@ -974,6 +1029,9 @@ extension GameCollectionViewController
let game = self.dataSource.item(at: indexPath)
let actions = self.actions(for: game)
let cell = self.collectionView.cellForItem(at: indexPath)
self._popoverSourceView = cell
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: { [weak self] in
guard let self = self else { return nil }

View File

@ -47,6 +47,7 @@ class GamesViewController: UIViewController
private let fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>
private var searchController: RSTSearchController?
private lazy var importController: ImportController = self.makeImportController()
private var syncingToastView: RSTToastView? {
didSet {
@ -58,6 +59,8 @@ class GamesViewController: UIViewController
}
private var syncingProgressObservation: NSKeyValueObservation?
@IBOutlet private var importButton: UIBarButtonItem!
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
fatalError("initWithNibName: not implemented")
}
@ -114,10 +117,16 @@ extension GamesViewController
let navigationBarAppearance = navigationController.navigationBar.standardAppearance.copy()
navigationBarAppearance.backgroundEffect = UIBlurEffect(style: .dark)
navigationController.navigationBar.standardAppearance = navigationBarAppearance
navigationController.navigationBar.scrollEdgeAppearance = navigationBarAppearance
let toolbarAppearance = navigationController.toolbar.standardAppearance.copy()
toolbarAppearance.backgroundEffect = UIBlurEffect(style: .dark)
navigationController.toolbar.standardAppearance = toolbarAppearance
navigationController.toolbar.standardAppearance = toolbarAppearance
if #available(iOS 15, *)
{
navigationController.toolbar.scrollEdgeAppearance = toolbarAppearance
}
}
else
{
@ -126,6 +135,22 @@ extension GamesViewController
}
}
if #available(iOS 14, *)
{
self.importController.presentingViewController = self
let importActions = self.importController.makeActions().menuActions
let importMenu = UIMenu(title: NSLocalizedString("Import From…", comment: ""), image: UIImage(systemName: "square.and.arrow.down"), children: importActions)
self.importButton.menu = importMenu
self.importButton.action = nil
self.importButton.target = nil
}
else
{
self.importController.barButtonItem = self.importButton
}
self.prepareSearchController()
self.updateTheme()
@ -352,8 +377,8 @@ private extension GamesViewController
/// Importing
extension GamesViewController: ImportControllerDelegate
{
@IBAction private func importFiles()
{
private func makeImportController() -> ImportController
{
var documentTypes = Set(System.registeredSystems.map { $0.gameType.rawValue })
documentTypes.insert(kUTTypeZipArchive as String)
documentTypes.insert("com.rileytestut.delta.skin")
@ -373,7 +398,13 @@ extension GamesViewController: ImportControllerDelegate
let importController = ImportController(documentTypes: documentTypes)
importController.delegate = self
importController.importOptions = [itunesImportOption]
self.present(importController, animated: true, completion: nil)
return importController
}
@IBAction private func importFiles()
{
self.present(self.importController, animated: true, completion: nil)
}
func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>, errors: [Error])

View File

@ -13,7 +13,7 @@ import DeltaCore
struct iTunesImportOption: ImportOption
{
let title = NSLocalizedString("iTunes", comment: "")
let image: UIImage? = nil
let image: UIImage? = UIImage(symbolNameIfAvailable: "music.note")
private let presentingViewController: UIViewController

View File

@ -37,7 +37,10 @@ class ImportController: NSObject
var delegate: ImportControllerDelegate?
var importOptions: [ImportOption]?
private weak var presentingViewController: UIViewController?
weak var presentingViewController: UIViewController?
weak var barButtonItem: UIBarButtonItem?
weak var sourceView: UIView?
// Store presentedViewController separately, since when we dismiss we don't know if it has already been dismissed.
// Calling dismiss on presentingViewController in that case would dismiss presentingViewController, which is bad.
@ -61,26 +64,54 @@ class ImportController: NSObject
super.init()
}
func makeActions() -> [Action]
{
assert(self.presentingViewController != nil, "presentingViewController must be set before calling makeActions()")
var actions = (self.importOptions ?? []).map { (option) -> Action in
let action = Action(title: option.title, style: .default, image: option.image) { _ in
option.import { importedURLs in
self.finish(with: importedURLs, errors: [])
}
}
return action
}
let filesAction = Action(title: NSLocalizedString("Files", comment: ""), style: .default, image: UIImage(symbolNameIfAvailable: "doc")) { action in
self.presentDocumentBrowser()
}
actions.append(filesAction)
return actions
}
fileprivate func presentImportController(from presentingViewController: UIViewController, animated: Bool, completionHandler: (() -> Void)?)
{
self.presentingViewController = presentingViewController
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction.cancel)
let actions = self.makeActions()
if let importOptions = self.importOptions
if actions.count > 1
{
for importOption in importOptions
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction.cancel)
let alertActions = actions.map { UIAlertAction($0) }
for action in alertActions
{
alertController.add(importOption) { [unowned self] (urls) in
self.finish(with: urls, errors: [])
}
alertController.addAction(action)
}
let filesAction = UIAlertAction(title: NSLocalizedString("Files", comment: ""), style: .default) { (action) in
self.presentDocumentBrowser()
if let sourceView = self.sourceView
{
alertController.popoverPresentationController?.sourceView = sourceView.superview
alertController.popoverPresentationController?.sourceRect = sourceView.frame
}
else
{
alertController.popoverPresentationController?.barButtonItem = self.barButtonItem
}
alertController.addAction(filesAction)
self.presentedViewController = alertController
self.presentingViewController?.present(alertController, animated: true, completion: nil)
@ -198,7 +229,7 @@ private var ImportControllerKey: UInt8 = 0
extension UIViewController
{
fileprivate(set) var importController: ImportController?
fileprivate var importController: ImportController?
{
set
{

View File

@ -12,13 +12,26 @@ import DeltaCore
extension CheatValidator
{
enum Error: Swift.Error
enum Error: LocalizedError
{
case invalidCode
case invalidName
case invalidGame
case duplicateName
case duplicateCode
case unknownCheatType
var errorDescription: String? {
switch self
{
case .invalidCode: return NSLocalizedString("The cheat code isn't in the correct format.", comment: "")
case .invalidName: return NSLocalizedString("The name of this cheat is invalid.", comment: "")
case .invalidGame: return NSLocalizedString("There is no associated game with this cheat.", comment: "")
case .duplicateName: return NSLocalizedString("A cheat already exists with this name.", comment: "")
case .duplicateCode: return NSLocalizedString("A cheat already exists with this code.", comment: "")
case .unknownCheatType: return NSLocalizedString("Delta does not support this cheat type.", comment: "")
}
}
}
}

View File

@ -8,8 +8,10 @@
import UIKit
import CoreData
import SwiftUI
import DeltaCore
import MelonDSDeltaCore
import Roxas
@ -30,6 +32,8 @@ class CheatsViewController: UITableViewController
weak var delegate: CheatsViewControllerDelegate?
private let dataSource = RSTFetchedResultsTableViewDataSource<Cheat>(fetchedResultsController: NSFetchedResultsController())
private var cheatBaseCheats: [CheatMetadata]?
}
extension CheatsViewController
@ -61,6 +65,21 @@ extension CheatsViewController
self.tableView.separatorEffect = vibrancyEffect
self.registerForPreviewing(with: self, sourceView: self.tableView)
if #available(iOS 14, *)
{
self.updateAddCheatMenu()
}
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
if #available(iOS 14, *), self.cheatBaseCheats == nil
{
self.fetchCheatBaseCheats()
}
}
override func didReceiveMemoryWarning()
@ -91,6 +110,37 @@ private extension CheatsViewController
self.dataSource.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: nil, cacheName: nil)
}
@available(iOS 14, *) @MainActor
func updateAddCheatMenu()
{
// CheatBase only contains DS cheats for now, so hide option completely for other systems.
guard self.game.type == .ds else { return }
var searchCheatBaseTitle = NSLocalizedString("Search CheatBase", comment: "")
var attributes: UIMenuElement.Attributes = []
if let cheats = self.cheatBaseCheats, cheats.isEmpty
{
searchCheatBaseTitle = NSLocalizedString("No Cheats in CheatBase", comment: "")
attributes = [.disabled]
}
let addCheatMenu = UIMenu(children: [
UIAction(title: NSLocalizedString("New Cheat Code", comment: ""), image: UIImage(systemName: "square.and.pencil")) { [weak self] _ in
self?.addCheat()
},
UIAction(title: searchCheatBaseTitle, image: UIImage(systemName: "magnifyingglass"), attributes: attributes) { [weak self] _ in
self?.searchCheatBase()
},
])
self.navigationItem.rightBarButtonItem?.target = nil
self.navigationItem.rightBarButtonItem?.action = nil
self.navigationItem.rightBarButtonItem?.menu = addCheatMenu
}
}
//MARK: - Managing Cheats -
@ -103,6 +153,78 @@ private extension CheatsViewController
editCheatViewController.presentWithPresentingViewController(self)
}
@available(iOS 14, *)
func fetchCheatBaseCheats()
{
Task {
do
{
let cheatBase = try CheatBase()
let cheats = try await cheatBase.cheats(for: self.game) ?? []
self.cheatBaseCheats = cheats
self.updateAddCheatMenu()
}
catch
{
print("[RSTLog] Failed to prefetch cheats from CheatBase:", error)
}
}
}
@available(iOS 14, *)
func searchCheatBase()
{
var rootView = CheatBaseView(game: self.game, cheats: self.cheatBaseCheats)
rootView.cancellationHandler = { [weak self] in
self?.presentedViewController?.dismiss(animated: true)
}
rootView.selectionHandler = { [weak self] cheatMetadata in
self?.saveCheatMetadata(cheatMetadata)
self?.presentedViewController?.dismiss(animated: true)
}
let hostingController = UIHostingController(rootView: rootView)
self.present(hostingController, animated: true, completion: nil)
}
func saveCheatMetadata(_ cheatMetadata: CheatMetadata)
{
DatabaseManager.shared.performBackgroundTask { context in
do
{
guard let cheatType = cheatMetadata.device.cheatType, let cheatFormat = cheatMetadata.device.cheatFormat else { throw CheatValidator.Error.unknownCheatType }
let cheat = Cheat(context: context)
cheat.name = cheatMetadata.name
cheat.type = cheatType
cheat.isEnabled = true
let sanitizedCode = cheatMetadata.code.components(separatedBy: .whitespacesAndNewlines).joined()
let formattedCode = sanitizedCode.formatted(with: cheatFormat)
cheat.code = formattedCode
let game = context.object(with: self.game.objectID) as! Game
cheat.game = game
let validator = CheatValidator(format: cheatFormat, managedObjectContext: context)
try validator.validate(cheat)
self.delegate?.cheatsViewController(self, activateCheat: cheat)
try context.save()
}
catch
{
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Unable to Add Cheat", comment: ""), error: error)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
func deleteCheat(_ cheat: Cheat)
{
self.delegate?.cheatsViewController(self, deactivateCheat: cheat)

View File

@ -93,14 +93,6 @@ extension SaveStatesViewController
self.collectionView?.dataSource = self.dataSource
self.collectionView?.prefetchDataSource = self.dataSource
let collectionViewLayout = self.collectionViewLayout as! GridCollectionViewLayout
let averageHorizontalInset = (collectionViewLayout.sectionInset.left + collectionViewLayout.sectionInset.right) / 2
let portraitScreenWidth = UIScreen.main.coordinateSpace.convert(UIScreen.main.bounds, to: UIScreen.main.fixedCoordinateSpace).width
// Use dimensions that allow two cells to fill the screen horizontally with padding in portrait mode
// We'll keep the same size for landscape orientation, which will allow more to fit
collectionViewLayout.itemWidth = floor((portraitScreenWidth - (averageHorizontalInset * 3)) / 2)
switch self.mode
{
case .saving:
@ -113,8 +105,7 @@ extension SaveStatesViewController
self.navigationItem.rightBarButtonItems?.removeFirst()
}
// Manually update prototype cell properties
self.prototypeCellWidthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionViewLayout.itemWidth)
self.prototypeCellWidthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: 0)
self.prototypeCellWidthConstraint.isActive = true
self.prepareEmulatorCoreSaveState()
@ -238,6 +229,26 @@ private extension SaveStatesViewController
}
self.sortButton.transform = CGAffineTransform.identity.rotated(by: Settings.sortSaveStatesByOldestFirst ? 0 : .pi)
let collectionViewLayout = self.collectionViewLayout as! GridCollectionViewLayout
if self.traitCollection.horizontalSizeClass == .regular
{
collectionViewLayout.itemWidth = 180
collectionViewLayout.minimumInteritemSpacing = 30
}
else
{
let averageHorizontalInset = (collectionViewLayout.sectionInset.left + collectionViewLayout.sectionInset.right) / 2
let portraitScreenWidth = UIScreen.main.coordinateSpace.convert(UIScreen.main.bounds, to: UIScreen.main.fixedCoordinateSpace).width
// Use dimensions that allow two cells to fill the screen horizontally with padding in portrait mode
// We'll keep the same size for landscape orientation, which will allow more to fit
collectionViewLayout.itemWidth = floor((portraitScreenWidth - (averageHorizontalInset * 3)) / 2)
}
// Manually update prototype cell properties
self.prototypeCellWidthConstraint.constant = collectionViewLayout.itemWidth
}
//MARK: - Configure Views -

190
Delta/SceneDelegate.swift Normal file
View File

@ -0,0 +1,190 @@
//
// SceneDelegate.swift
// Delta
//
// Created by Riley Testut on 6/6/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import UIKit
import DeltaCore
import Harmony
@objc(SceneDelegate) @available(iOS 13, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate
{
var window: UIWindow? {
get {
if _window == nil
{
_window = GameWindow()
}
return _window
}
set {
_window = newValue as? GameWindow
}
}
private var _window: GameWindow?
private let deepLinkController = DeepLinkController()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
{
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
self.window?.tintColor = .deltaPurple
if let context = connectionOptions.urlContexts.first
{
self.handle(.url(context.url))
}
if let shortcutItem = connectionOptions.shortcutItem
{
self.handle(.shortcut(shortcutItem))
}
self.window?.makeKeyAndVisible()
}
func sceneDidDisconnect(_ scene: UIScene)
{
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene)
{
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene)
{
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene)
{
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene)
{
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
@available(iOS 13, *)
extension SceneDelegate
{
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)
{
guard let context = URLContexts.first else { return }
self.handle(.url(context.url))
}
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void)
{
self.handle(.shortcut(shortcutItem))
completionHandler(true)
}
}
@available(iOS 13, *)
private extension SceneDelegate
{
func handle(_ deepLink: DeepLink)
{
guard DatabaseManager.shared.isStarted else {
// Wait until DatabaseManager is ready before handling deep link.
// NotificationCenter.default.notifications requires iOS 15 or later :(
// _ = await NotificationCenter.default.notifications(named: DatabaseManager.didStartNotification).first(where: { _ in true })
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(forName: DatabaseManager.didStartNotification, object: DatabaseManager.shared, queue: .main) { [weak observer] _ in
observer.map { NotificationCenter.default.removeObserver($0) }
self.handle(deepLink)
}
return
}
DispatchQueue.main.async {
// DeepLinkController expects to be called from main thread.
switch deepLink
{
case .shortcut:
_ = self.deepLinkController.handle(deepLink)
case .url(let url):
if url.isFileURL
{
if GameType(fileExtension: url.pathExtension) != nil || url.pathExtension.lowercased() == "zip"
{
self.importGame(at: url)
}
else if url.pathExtension.lowercased() == "deltaskin"
{
self.importControllerSkin(at: url)
}
}
else if url.scheme?.hasPrefix("db-") == true
{
_ = DropboxService.shared.handleDropboxURL(url)
}
else if url.scheme?.lowercased() == "delta"
{
_ = self.deepLinkController.handle(deepLink)
}
}
}
}
func importGame(at url: URL)
{
DatabaseManager.shared.importGames(at: [url]) { (games, errors) in
if errors.count > 0
{
let alertController = UIAlertController.alertController(for: .games, with: errors)
self.present(alertController)
}
}
}
func importControllerSkin(at url: URL)
{
DatabaseManager.shared.importControllerSkins(at: [url]) { (games, errors) in
if errors.count > 0
{
let alertController = UIAlertController.alertController(for: .controllerSkins, with: errors)
self.present(alertController)
}
}
}
func present(_ alertController: UIAlertController)
{
var rootViewController = self.window?.rootViewController
while rootViewController?.presentedViewController != nil
{
rootViewController = rootViewController?.presentedViewController
}
rootViewController?.present(alertController, animated: true, completion: nil)
}
}

View File

@ -14,7 +14,7 @@ import DeltaCore
import Roxas
@objc(SwitchTableViewCell)
private class SwitchTableViewCell: UITableViewCell
class SwitchTableViewCell: UITableViewCell
{
@IBOutlet var switchView: UISwitch!
}

View File

@ -0,0 +1,45 @@
//
// Contributor.swift
// Delta
//
// Created by Riley Testut on 2/3/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import Foundation
struct Contributor: Identifiable, Decodable
{
var name: String
var id: String {
// Use names as identifiers for now.
return self.name
}
var url: URL? {
guard let link = self.link, let url = URL(string: link) else { return nil }
return url
}
private var link: String?
var linkName: String?
var contributions: [Contribution]
}
struct Contribution: Identifiable, Decodable
{
var name: String
var id: String {
// Use names as identifiers for now.
return self.name
}
var url: URL? {
guard let link = self.link, let url = URL(string: link) else { return nil }
return url
}
private var link: String?
}

View File

@ -0,0 +1,200 @@
//
// ContributionsView.swift
// Delta
//
// Created by Riley Testut on 2/2/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import SwiftUI
import SafariServices
@available(iOS 14, *)
private extension NavigationLink where Label == EmptyView, Destination == EmptyView
{
// Copied from https://stackoverflow.com/a/66891173
static var empty: NavigationLink {
self.init(destination: EmptyView(), label: { EmptyView() })
}
}
@available(iOS 14, *)
extension ContributorsView
{
fileprivate class ViewModel: ObservableObject
{
@Published
var contributors: [Contributor]?
@Published
var error: Error?
@Published
var webViewURL: URL?
weak var hostingController: UIViewController?
func loadContributors()
{
guard self.contributors == nil else { return }
do
{
let fileURL = Bundle.main.url(forResource: "Contributors", withExtension: "plist")!
let data = try Data(contentsOf: fileURL)
let contributors = try PropertyListDecoder().decode([Contributor].self, from: data)
self.contributors = contributors
}
catch
{
self.error = error
}
}
}
static func makeViewController() -> UIHostingController<some View>
{
let viewModel = ViewModel()
let contributorsView = ContributorsView(viewModel: viewModel)
let hostingController = UIHostingController(rootView: contributorsView)
hostingController.title = NSLocalizedString("Contributors", comment: "")
viewModel.hostingController = hostingController
return hostingController
}
}
@available(iOS 14, *)
struct ContributorsView: View
{
@StateObject
private var viewModel: ViewModel
@State
private var showErrorAlert: Bool = false
var body: some View {
List {
Section(content: {}, footer: {
Text("These individuals have contributed to the open-source Delta project on GitHub.\n\nThank you to all our contributors, your help is much appreciated 💜")
.font(.subheadline)
})
ForEach(viewModel.contributors ?? []) { contributor in
Section {
// First row = contributor
ContributionCell(name: Text(contributor.name).bold(), url: contributor.url, linkName: contributor.linkName) { webViewURL in
viewModel.webViewURL = webViewURL
}
// Remaining rows = contributions
ForEach(contributor.contributions) { contribution in
ContributionCell(name: Text(contribution.name), url: contribution.url) { webViewURL in
viewModel.webViewURL = webViewURL
}
}
}
}
}
.listStyle(.grouped) // TODO: Change to .insetGrouped once we drop iOS 13 support.
.environmentObject(viewModel)
.alert(isPresented: $showErrorAlert) {
Alert(title: Text("Unable to Load Contributors"), message: Text(viewModel.error?.localizedDescription ?? ""), dismissButton: .default(Text("OK")) {
guard let hostingController = viewModel.hostingController else { return }
hostingController.navigationController?.popViewController(animated: true)
})
}
.onReceive(viewModel.$error) { error in
guard error != nil else { return }
showErrorAlert = true
}
.onReceive(viewModel.$webViewURL) { webViewURL in
guard let webViewURL else { return }
openURL(webViewURL)
}
.onAppear {
viewModel.loadContributors()
}
}
fileprivate init(contributors: [Contributor]? = nil, viewModel: ViewModel = ViewModel())
{
if let contributors
{
// Don't overwrite passed-in viewModel.contributors if contributors is nil.
viewModel.contributors = contributors
}
self._viewModel = StateObject(wrappedValue: viewModel)
}
}
@available(iOS 14, *)
struct ContributionCell: View
{
var name: Text
var url: URL?
var linkName: String?
var action: (URL) -> Void
var body: some View {
let body = Button {
guard let url else { return }
Task { @MainActor in
// Dispatch Task to avoid "Publishing changes from within view updates is not allowed, this will cause undefined behavior." runtime error on iOS 16.
self.action(url)
}
} label: {
HStack {
self.name
.font(.system(size: 17)) // Match Settings screen
Spacer()
if let linkName
{
Text(linkName)
.font(.system(size: 17)) // Match Settings screen
.foregroundColor(.gray)
}
if url != nil
{
NavigationLink.empty
.fixedSize()
}
}
}
.accentColor(.primary)
if url != nil
{
body
}
else
{
// No URL to open, so disable cell highlighting.
body.buttonStyle(.plain)
}
}
}
@available(iOS 14, *)
private extension ContributorsView
{
func openURL(_ url: URL)
{
guard let hostingController = viewModel.hostingController else { return }
let safariViewController = SFSafariViewController(url: url)
safariViewController.preferredControlTintColor = .deltaPurple
hostingController.present(safariViewController, animated: true)
}
}

View File

@ -40,6 +40,8 @@ class ControllerInputsViewController: UIViewController
private var activeCalloutView: InputCalloutView?
private var _didLayoutSubviews = false
@IBOutlet private var actionsMenuViewControllerHeightConstraint: NSLayoutConstraint!
@IBOutlet private var cancelTapGestureRecognizer: UITapGestureRecognizer!
@ -65,7 +67,15 @@ class ControllerInputsViewController: UIViewController
self.gameViewController.controllerView.addReceiver(self)
self.navigationController?.navigationBar.barStyle = .black
if let navigationController = self.navigationController, #available(iOS 13, *)
{
navigationController.overrideUserInterfaceStyle = .dark
navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance // Fixes invisible navigation bar on iPad.
}
else
{
self.navigationController?.navigationBar.barStyle = .black
}
NSLayoutConstraint.activate([self.gameViewController.gameView.centerYAnchor.constraint(equalTo: self.actionsMenuViewController.view.centerYAnchor)])
@ -81,6 +91,23 @@ class ControllerInputsViewController: UIViewController
{
self.actionsMenuViewControllerHeightConstraint.constant = self.actionsMenuViewController.preferredContentSize.height
}
if let window = self.view.window, !_didLayoutSubviews
{
var traits = DeltaCore.ControllerSkin.Traits.defaults(for: window)
traits.orientation = .portrait
if traits.device == .ipad
{
// Use standard iPhone skins instead of iPad skins.
traits.device = .iphone
traits.displayType = .standard
}
self.gameViewController.controllerView.overrideControllerSkinTraits = traits
_didLayoutSubviews = true
}
}
override func viewDidAppear(_ animated: Bool)
@ -91,6 +118,9 @@ class ControllerInputsViewController: UIViewController
{
self.prepareCallouts()
}
// controllerView must be first responder to receive keyboard presses.
self.gameViewController.controllerView.becomeFirstResponder()
}
}
@ -183,6 +213,10 @@ private extension ControllerInputsViewController
listMenuViewController.title = NSLocalizedString("Game System", comment: "")
let navigationController = UINavigationController(rootViewController: listMenuViewController)
if #available(iOS 13, *)
{
navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance
}
let popoverMenuController = PopoverMenuController(popoverViewController: navigationController)
self.navigationItem.popoverMenuController = popoverMenuController
@ -403,6 +437,7 @@ private extension ControllerInputsViewController
}
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.popoverPresentationController?.barButtonItem = sender
alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Reset Controls to Defaults", comment: ""), style: .destructive, handler: { (action) in
reset()
@ -418,6 +453,12 @@ extension ControllerInputsViewController: UIGestureRecognizerDelegate
return self.activeCalloutView != nil
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
{
// Necessary to prevent other gestures (e.g. GameViewController's resumeEmulationIfNeeded() tap gesture) from cancelling tap.
return true
}
@IBAction private func handleTapGesture(_ tapGestureRecognizer: UITapGestureRecognizer)
{
self.updateActiveCalloutView(with: nil)
@ -532,13 +573,19 @@ extension ControllerInputsViewController: GameControllerReceiver
{
func gameController(_ gameController: GameController, didActivate controllerInput: DeltaCore.Input, value: Double)
{
guard self.isViewLoaded else { return }
guard self.isViewLoaded, value > 0.9 else { return }
switch gameController
{
case self.gameViewController.controllerView:
if let calloutView = self.calloutViews[AnyInput(controllerInput)]
{
if controllerInput.isContinuous
{
// Make sure we only toggle calloutView once in a single gesture.
guard calloutView.state == .normal else { break }
}
self.toggle(calloutView)
}
@ -562,3 +609,15 @@ extension ControllerInputsViewController: SMCalloutViewDelegate
self.toggle(calloutView)
}
}
extension ControllerInputsViewController: UIAdaptivePresentationControllerDelegate
{
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
{
switch (traitCollection.horizontalSizeClass, traitCollection.verticalSizeClass)
{
case (.regular, .regular): return .formSheet // Regular width and height, so display as form sheet
default: return .fullScreen // Compact width and/or height, so display full screen
}
}
}

View File

@ -108,9 +108,18 @@ extension ControllersSettingsViewController
switch identifier
{
case "controllerInputsSegue":
let controllerInputsViewController = (segue.destination as! UINavigationController).topViewController as! ControllerInputsViewController
let navigationController = segue.destination as! UINavigationController
let controllerInputsViewController = navigationController.topViewController as! ControllerInputsViewController
controllerInputsViewController.gameController = self.gameController
if self.view.traitCollection.userInterfaceIdiom == .pad
{
// For now, only iPads can display ControllerInputsViewController as a form sheet.
navigationController.modalPresentationStyle = .formSheet
navigationController.presentationController?.delegate = controllerInputsViewController
}
default: break
}
@ -296,6 +305,8 @@ extension ControllersSettingsViewController
{
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let previousGameController = self.gameController
switch Section(rawValue: indexPath.section)!
{
case .localDevice: self.gameController = self.localDeviceController
@ -310,7 +321,7 @@ extension ControllersSettingsViewController
let previousIndexPath: IndexPath?
if let gameController = self.gameController
if let gameController = previousGameController
{
if gameController == self.localDeviceController
{

View File

@ -23,18 +23,18 @@ private extension MelonDSCoreSettingsViewController
enum Section: Int
{
case general
case performance
case dsBIOS
case dsiBIOS
case changeCore
}
@available(iOS 13, *)
enum BIOSError: LocalizedError
{
case unknownSize(URL)
case incorrectHash(URL, hash: String, expectedHash: String)
case unsupportedHash(URL, hash: String)
@available(iOS 13, *)
case incorrectSize(URL, size: Int, validSizes: Set<ClosedRange<Measurement<UnitInformationStorage>>>)
private static let byteFormatter: ByteCountFormatter = {
@ -150,6 +150,13 @@ private extension MelonDSCoreSettingsViewController
switch section
{
case .performance:
// Hide AltJIT section for public builds.
guard isBeta else { return true }
guard Settings.preferredCore(for: .ds) == MelonDS.core else { return true }
return !UIDevice.current.supportsJIT
case .dsBIOS where Settings.preferredCore(for: .ds) == DS.core:
// Using DeSmuME core, which doesn't require BIOS.
return true
@ -253,6 +260,11 @@ private extension MelonDSCoreSettingsViewController
}
}
@IBAction func toggleAltJITEnabled(_ sender: UISwitch)
{
Settings.isAltJITEnabled = sender.isOn
}
@objc func willEnterForeground(_ notification: Notification)
{
self.tableView.reloadData()
@ -301,6 +313,10 @@ extension MelonDSCoreSettingsViewController
cell.contentView.isHidden = (item == nil)
case .performance:
let cell = cell as! SwitchTableViewCell
cell.switchView.isOn = Settings.isAltJITEnabled
case .dsBIOS:
let bios = DSBIOS.allCases[indexPath.row]
@ -379,6 +395,8 @@ extension MelonDSCoreSettingsViewController
case .changeCore:
self.changeCore()
case .performance: break
}
}

View File

@ -38,6 +38,8 @@ extension Settings
case syncingService
case isButtonHapticFeedbackEnabled
case isThumbstickHapticFeedbackEnabled
case isAltJITEnabled
case respectSilentMode
}
}
@ -60,8 +62,18 @@ struct Settings
#keyPath(UserDefaults.isThumbstickHapticFeedbackEnabled): true,
#keyPath(UserDefaults.sortSaveStatesByOldestFirst): true,
#keyPath(UserDefaults.isPreviewsEnabled): true,
#keyPath(UserDefaults.isAltJITEnabled): false,
#keyPath(UserDefaults.respectSilentMode): true,
Settings.preferredCoreSettingsKey(for: .ds): MelonDS.core.identifier] as [String : Any]
UserDefaults.standard.register(defaults: defaults)
#if !BETA
// Manually set MelonDS as preferred DS core in case DeSmuME is cached from a previous version.
UserDefaults.standard.set(MelonDS.core.identifier, forKey: Settings.preferredCoreSettingsKey(for: .ds))
// Manually disable AltJIT for public builds.
UserDefaults.standard.isAltJITEnabled = false
#endif
}
}
@ -190,6 +202,28 @@ extension Settings
}
}
static var isAltJITEnabled: Bool {
get {
let isAltJITEnabled = UserDefaults.standard.isAltJITEnabled
return isAltJITEnabled
}
set {
UserDefaults.standard.isAltJITEnabled = newValue
NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [NotificationUserInfoKey.name: Name.isAltJITEnabled])
}
}
static var respectSilentMode: Bool {
get {
let respectSilentMode = UserDefaults.standard.respectSilentMode
return respectSilentMode
}
set {
UserDefaults.standard.respectSilentMode = newValue
NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [NotificationUserInfoKey.name: Name.respectSilentMode])
}
}
static func preferredCore(for gameType: GameType) -> DeltaCoreProtocol?
{
let key = self.preferredCoreSettingsKey(for: gameType)
@ -385,4 +419,8 @@ private extension UserDefaults
@NSManaged var sortSaveStatesByOldestFirst: Bool
@NSManaged var isPreviewsEnabled: Bool
@NSManaged var isAltJITEnabled: Bool
@NSManaged var respectSilentMode: Bool
}

View File

@ -20,6 +20,7 @@ private extension SettingsViewController
case controllers
case controllerSkins
case controllerOpacity
case gameAudio
case hapticFeedback
case syncing
case hapticTouch
@ -44,9 +45,11 @@ private extension SettingsViewController
enum CreditsRow: Int, CaseIterable
{
case riley
case shane
case caroline
case grant
case litRitt
case contributors
case softwareLicenses
}
}
@ -56,6 +59,7 @@ class SettingsViewController: UITableViewController
@IBOutlet private var controllerOpacityLabel: UILabel!
@IBOutlet private var controllerOpacitySlider: UISlider!
@IBOutlet private var respectSilentModeSwitch: UISwitch!
@IBOutlet private var buttonHapticFeedbackEnabledSwitch: UISwitch!
@IBOutlet private var thumbstickHapticFeedbackEnabledSwitch: UISwitch!
@IBOutlet private var previewsEnabledSwitch: UISwitch!
@ -160,6 +164,8 @@ private extension SettingsViewController
self.controllerOpacitySlider.value = Float(Settings.translucentControllerSkinOpacity)
self.updateControllerOpacityLabel()
self.respectSilentModeSwitch.isOn = Settings.respectSilentMode
self.syncingServiceLabel.text = Settings.syncingService?.localizedName
do
@ -248,6 +254,11 @@ private extension SettingsViewController
Settings.isPreviewsEnabled = sender.isOn
}
@IBAction func toggleRespectSilentMode(_ sender: UISwitch)
{
Settings.respectSilentMode = sender.isOn
}
func openTwitter(username: String)
{
let twitterAppURL = URL(string: "twitter://user?screen_name=" + username)!
@ -269,6 +280,13 @@ private extension SettingsViewController
}
}
}
@available(iOS 14, *)
func showContributors()
{
let hostingController = ContributorsView.makeViewController()
self.navigationController?.pushViewController(hostingController, animated: true)
}
}
private extension SettingsViewController
@ -290,7 +308,7 @@ private extension SettingsViewController
self.tableView.selectRow(at: selectedIndexPath, animated: true, scrollPosition: .none)
}
case .localControllerPlayerIndex, .preferredControllerSkin, .translucentControllerSkinOpacity, .isButtonHapticFeedbackEnabled, .isThumbstickHapticFeedbackEnabled: break
case .localControllerPlayerIndex, .preferredControllerSkin, .translucentControllerSkinOpacity, .respectSilentMode, .isButtonHapticFeedbackEnabled, .isThumbstickHapticFeedbackEnabled, .isAltJITEnabled: break
}
}
@ -367,7 +385,7 @@ extension SettingsViewController
let preferredCore = Settings.preferredCore(for: .ds)
cell.detailTextLabel?.text = preferredCore?.metadata?.name.value ?? preferredCore?.name ?? NSLocalizedString("Unknown", comment: "")
case .controllerOpacity, .hapticFeedback, .hapticTouch, .patreon, .credits: break
case .controllerOpacity, .gameAudio, .hapticFeedback, .hapticTouch, .patreon, .credits: break
}
return cell
@ -383,7 +401,7 @@ extension SettingsViewController
case .controllers: self.performSegue(withIdentifier: Segue.controllers.rawValue, sender: cell)
case .controllerSkins: self.performSegue(withIdentifier: Segue.controllerSkins.rawValue, sender: cell)
case .cores: self.performSegue(withIdentifier: Segue.dsSettings.rawValue, sender: cell)
case .controllerOpacity, .hapticFeedback, .hapticTouch, .syncing: break
case .controllerOpacity, .gameAudio, .hapticFeedback, .hapticTouch, .syncing: break
case .patreon:
let patreonURL = URL(string: "altstore://patreon")!
@ -404,9 +422,14 @@ extension SettingsViewController
switch row
{
case .riley: self.openTwitter(username: "rileytestut")
case .shane: self.openTwitter(username: "shanegillio")
case .caroline: self.openTwitter(username: "1carolinemoore")
case .grant: self.openTwitter(username: "grantgliner")
case .litRitt: self.openTwitter(username: "litritt_z")
case .litRitt: self.openTwitter(username: "lit_ritt")
case .contributors:
guard #available(iOS 14, *) else { return }
self.showContributors()
case .softwareLicenses: break
}
}
@ -414,13 +437,35 @@ extension SettingsViewController
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
primary:
switch Section(rawValue: indexPath.section)!
{
#if !BETA
case .credits where indexPath.row == CreditsRow.litRitt.rawValue: return 0.0
#endif
default: return super.tableView(tableView, heightForRowAt: indexPath)
case .credits:
let row = CreditsRow(rawValue: indexPath.row)!
switch row
{
case .grant:
// Hide row on iOS 14 and above
guard #available(iOS 14, *) else { break primary }
return 0.0
case .litRitt:
// Hide row on iOS 14 and above
guard #available(iOS 14, *) else { break primary }
return 0.0
case .contributors:
// Hide row on iOS 13 and below
guard #unavailable(iOS 14) else { break primary }
return 0.0
default: break
}
default: break
}
return super.tableView(tableView, heightForRowAt: indexPath)
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?

View File

@ -203,6 +203,27 @@
<string>Delta uses your microphone to emulate the Nintendo DS microphone.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Press "OK" to allow Delta to use images from your Photo Library as game artwork.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISceneConfigurationName</key>
<string>Main</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array/>
<key>UIFileSharingEnabled</key>
@ -215,6 +236,8 @@
<array>
<string>armv7</string>
</array>
<key>UIRequiresFullScreen</key>
<false/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UISupportedInterfaceOrientations</key>
@ -223,6 +246,13 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>UTExportedTypeDeclarations</key>
@ -400,5 +430,13 @@
</dict>
</dict>
</array>
<key>NSBonjourServices</key>
<array>
<string>_altserver._tcp</string>
</array>
<key>ALTDeviceID</key>
<string>00008110-000A68390A82801E</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Delta uses the local network to communicate with AltServer and enable JIT.</string>
</dict>
</plist>

View File

@ -72,7 +72,7 @@ extension DeltaCoreProtocol
case GBA.core: return 3
case N64.core where UIDevice.current.hasA11ProcessorOrBetter: return 3
case N64.core where UIDevice.current.hasA9ProcessorOrBetter: return 1.5
case MelonDS.core where UIDevice.current.supportsJIT: return 3
case MelonDS.core where ProcessInfo.processInfo.isJITAvailable: return 3
case MelonDS.core where UIDevice.current.hasA11ProcessorOrBetter: return 1.5
case GPGX.core: return 4
default: return 1

1
External/CheatBase vendored Submodule

@ -0,0 +1 @@
Subproject commit df52576e6fd1185747f41b022d9664ad0593608a

2
External/Harmony vendored

@ -1 +1 @@
Subproject commit db5fbd829ac5aa6e7e249eb9fabf07bbb75fabd9
Subproject commit 7234d6626a49e56ddceaaec0c04cc4f4f43b572c

View File

@ -138,7 +138,7 @@ SPEC CHECKSUMS:
GTMSessionFetcher: b3503b20a988c4e20cc189aa798fd18220133f52
Harmony: cea514db17c41c22f78f54b17d2135935b5e9b96
MelonDSDeltaCore: 3de2a2e2ebcd630a6dd756818b5a26dde7afa726
N64DeltaCore: 6b0f07f2078193a15d736f153ce701661853fa36
N64DeltaCore: c513f36f88d421e79fda3cd7963040db84d3ab51
NESDeltaCore: 41ab438dd78d51d4636aacb7d9a7336ea3d4728c
Roxas: 1990039f843f5dc284918dc82375feb80020ef62
SDWebImage: a72e880a8fe0f7fc31efe15aaed443c074d2a80c
@ -148,6 +148,6 @@ SPEC CHECKSUMS:
SwiftyDropbox: 378b4425a2e8d0cb24c7b0f2e3af72bfbaaf1e73
ZIPFoundation: b1f0de4eed33e74a676f76e12559ab6b75990197
PODFILE CHECKSUM: 2f9219b1db18479e650fc2159f58160b4cc13aad
PODFILE CHECKSUM: 68bb9968a91dd904a9f75286dfb3c2536917eb87
COCOAPODS: 1.10.1
COCOAPODS: 1.11.2

View File

@ -51,6 +51,11 @@
"CLANG_ENABLE_MODULES": "NO",
"GCC_PREPROCESSOR_DEFINITIONS": "STATIC_LIBRARY=1"
},
"script_phases": {
"name": "Get GlideN64 Revision.h",
"script": "\"${PODS_TARGET_SRCROOT}/Mupen64Plus/GLideN64/src/getRevision.sh\"",
"execution_position": "before_compile"
},
"dependencies": {
"DeltaCore": [

6
Pods/Manifest.lock generated
View File

@ -138,7 +138,7 @@ SPEC CHECKSUMS:
GTMSessionFetcher: b3503b20a988c4e20cc189aa798fd18220133f52
Harmony: cea514db17c41c22f78f54b17d2135935b5e9b96
MelonDSDeltaCore: 3de2a2e2ebcd630a6dd756818b5a26dde7afa726
N64DeltaCore: 6b0f07f2078193a15d736f153ce701661853fa36
N64DeltaCore: c513f36f88d421e79fda3cd7963040db84d3ab51
NESDeltaCore: 41ab438dd78d51d4636aacb7d9a7336ea3d4728c
Roxas: 1990039f843f5dc284918dc82375feb80020ef62
SDWebImage: a72e880a8fe0f7fc31efe15aaed443c074d2a80c
@ -148,6 +148,6 @@ SPEC CHECKSUMS:
SwiftyDropbox: 378b4425a2e8d0cb24c7b0f2e3af72bfbaaf1e73
ZIPFoundation: b1f0de4eed33e74a676f76e12559ab6b75990197
PODFILE CHECKSUM: 2f9219b1db18479e650fc2159f58160b4cc13aad
PODFILE CHECKSUM: 68bb9968a91dd904a9f75286dfb3c2536917eb87
COCOAPODS: 1.10.1
COCOAPODS: 1.11.2

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Crashlytics
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Crashlytics
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

View File

@ -1,10 +0,0 @@
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Fabric
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Fabric/iOS"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Fabric
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

View File

@ -4,7 +4,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS" "${PODS_ROOT}/GoogleSignIn/Frameworks"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 STATIC_LIBRARY=1 STATIC_LIBRARY=1 JIT_ENABLED=1 STATIC_LIBRARY=1 MUPENPLUSAPI TXFILTER_LIB OS_IOS GLESX GL_ERROR_DEBUG GL_DEBUG GLESX PNG_ARM_NEON_OPT=0
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DSDeltaCore" "${PODS_ROOT}/Headers/Public/DeltaCore" "${PODS_ROOT}/Headers/Public/GBADeltaCore" "${PODS_ROOT}/Headers/Public/GBCDeltaCore" "${PODS_ROOT}/Headers/Public/GTMSessionFetcher" "${PODS_ROOT}/Headers/Public/GoogleAPIClientForREST" "${PODS_ROOT}/Headers/Public/GoogleToolboxForMac" "${PODS_ROOT}/Headers/Public/Harmony" "${PODS_ROOT}/Headers/Public/MelonDSDeltaCore" "${PODS_ROOT}/Headers/Public/N64DeltaCore" "${PODS_ROOT}/Headers/Public/NESDeltaCore" "${PODS_ROOT}/Headers/Public/Roxas" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/SMCalloutView" "${PODS_ROOT}/Headers/Public/SNESDeltaCore" "${PODS_ROOT}/Headers/Public/SQLite.swift" "${PODS_ROOT}/Headers/Public/SwiftyDropbox" "${PODS_CONFIGURATION_BUILD_DIR}" "$(PODS_ROOT)/Headers/Private/DSDeltaCore/desmume/desmume/src/libretro-common/include" "${PODS_CONFIGURATION_BUILD_DIR}" "$(PODS_ROOT)/Headers/Private/GBADeltaCore/SFML/include" "$(PODS_ROOT)/Headers/Private/GBADeltaCore/SFML/src" "${PODS_CONFIGURATION_BUILD_DIR}" "${PODS_CONFIGURATION_BUILD_DIR}" "$(PODS_ROOT)/Headers/Private/MelonDSDeltaCore/melonDS/src" "${PODS_CONFIGURATION_BUILD_DIR}" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/mupen64plus-core/subprojects/**" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/libMupen64Plus/SDL" "${PODS_CONFIGURATION_BUILD_DIR}" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/mupen64plus-core/subprojects/**" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/libMupen64Plus/SDL" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/GLideN64/src" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/GLideN64/src/inc" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/GLideN64/src" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/GLideN64/src/osal" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/libpng"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DSDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/DeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GBADeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GBCDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleToolboxForMac" "${PODS_CONFIGURATION_BUILD_DIR}/Harmony" "${PODS_CONFIGURATION_BUILD_DIR}/MelonDSDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/N64DeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/NESDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/Roxas" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SMCalloutView" "${PODS_CONFIGURATION_BUILD_DIR}/SNESDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyDropbox" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DSDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/DeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GBADeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GBCDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleToolboxForMac" "${PODS_CONFIGURATION_BUILD_DIR}/Harmony" "${PODS_CONFIGURATION_BUILD_DIR}/MelonDSDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/N64DeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/NESDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/Roxas" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SMCalloutView" "${PODS_CONFIGURATION_BUILD_DIR}/SNESDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyDropbox" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation" /usr/lib/swift
OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/DSDeltaCore/DSDeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/DeltaCore/DeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/GBADeltaCore/GBADeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/GBCDeltaCore/GBCDeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Harmony/Harmony.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/MelonDSDeltaCore/MelonDSDeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/N64DeltaCore/N64DeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/NESDeltaCore/NESDeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SNESDeltaCore/SNESDeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SwiftyDropbox/SwiftyDropbox.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation/ZIPFoundation.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/GTMSessionFetcher/GTMSessionFetcher.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/GoogleAPIClientForREST/GoogleAPIClientForREST.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/GoogleToolboxForMac/GoogleToolboxForMac.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/Roxas/Roxas.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/SDWebImage/SDWebImage.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/SMCalloutView/SMCalloutView.modulemap" -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/DSDeltaCore" -isystem "${PODS_ROOT}/Headers/Public/GBADeltaCore" -isystem "${PODS_ROOT}/Headers/Public/GBCDeltaCore" -isystem "${PODS_ROOT}/Headers/Public/Harmony" -isystem "${PODS_ROOT}/Headers/Public/MelonDSDeltaCore" -isystem "${PODS_ROOT}/Headers/Public/N64DeltaCore" -isystem "${PODS_ROOT}/Headers/Public/NESDeltaCore" -isystem "${PODS_ROOT}/Headers/Public/SNESDeltaCore" -iframework "${PODS_ROOT}/Crashlytics/iOS" -iframework "${PODS_ROOT}/Fabric/iOS" -iframework "${PODS_ROOT}/GoogleSignIn/Frameworks" -DHOST_DARWIN -DDESMUME_COCOA -DHAVE_OPENGL -DHAVE_LIBZ -DANDROID -fexceptions -ftree-vectorize -DCOMPRESS_MT -DIOS -DOBJ_C -marm -fvisibility=hidden -DSTATIC_LIBRARY=1 -DSTATIC_LIBRARY -DHAVE_CSTDINT -fno-strict-aliasing -DGCC -pthread -fPIC -D__unix__ -ffast-math -fno-strict-aliasing -DGCC -pthread -fPIC -D__unix__ -ffast-math -D__VEC4_OPT -fvisibility=hidden -funsigned-char -ffast-math -finline -fno-builtin -fno-common -fomit-frame-pointer -funroll-loops -fstrict-aliasing -DHAVE_STDINT_H -DARM -DSTATIC_LIBRARY=1
OTHER_LDFLAGS = $(inherited) -ObjC -Wl,-exported_symbol,_Video_PluginStartup,-exported_symbol,_Video_PluginShutdown,-exported_symbol,_Video_PluginGetVersion,-exported_symbol,_Video_RomOpen,-exported_symbol,_Video_RomClosed,-exported_symbol,_ConfigGetSharedDataFilepath,-exported_symbol,_ConfigGetUserConfigPath,-exported_symbol,_ConfigGetUserCachePath,-exported_symbol,_ConfigGetUserDataPath,-exported_symbol,_ConfigOpenSection,-exported_symbol,_ConfigDeleteSection,-exported_symbol,_ConfigSaveSection,-exported_symbol,_ConfigSaveFile,-exported_symbol,_ConfigSetDefaultInt,-exported_symbol,_ConfigSetDefaultFloat,-exported_symbol,_ConfigSetDefaultBool,-exported_symbol,_ConfigSetDefaultString,-exported_symbol,_ConfigGetParamInt,-exported_symbol,_ConfigGetParamFloat,-exported_symbol,_ConfigGetParamBool,-exported_symbol,_ConfigGetParamString,-exported_symbol,_ConfigExternalGetParameter,-exported_symbol,_ConfigExternalOpen,-exported_symbol,_ConfigExternalClose,-exported_symbol,_VidExt_Init,-exported_symbol,_VidExt_Quit,-exported_symbol,_VidExt_ListFullscreenModes,-exported_symbol,_VidExt_SetVideoMode,-exported_symbol,_VidExt_SetCaption,-exported_symbol,_VidExt_ToggleFullScreen,-exported_symbol,_VidExt_ResizeWindow,-exported_symbol,_VidExt_GL_GetProcAddress,-exported_symbol,_VidExt_GL_SetAttribute,-exported_symbol,_VidExt_GL_GetAttribute,-exported_symbol,_VidExt_GL_SwapBuffers,-exported_symbol,_ChangeWindow,-exported_symbol,_InitiateGFX,-exported_symbol,_MoveScreen,-exported_symbol,_ProcessDList,-exported_symbol,_ProcessRDPList,-exported_symbol,_ShowCFB,-exported_symbol,_UpdateScreen,-exported_symbol,_ViStatusChanged,-exported_symbol,_ViWidthChanged,-exported_symbol,_ReadScreen2,-exported_symbol,_SetRenderingCallback,-exported_symbol,_FBRead,-exported_symbol,_FBWrite,-exported_symbol,_FBGetFrameBufferInfo,-exported_symbol,_ResizeVideoOutput,-exported_symbol,_RSP_PluginStartup,-exported_symbol,_RSP_PluginShutdown,-exported_symbol,_RSP_PluginGetVersion,-exported_symbol,_DoRspCycles,-exported_symbol,_InitiateRSP,-exported_symbol,_RSP_RomClosed,-exported_symbol,_CoreGetAPIVersions,-exported_symbol,_ConfigGetParameter,-exported_symbol,_ConfigSetParameter,-exported_symbol,_CoreDoCommand -l"Alamofire" -l"DSDeltaCore" -l"GBADeltaCore" -l"GBCDeltaCore" -l"GTMSessionFetcher" -l"GoogleAPIClientForREST" -l"GoogleToolboxForMac" -l"Harmony" -l"MelonDSDeltaCore" -l"N64DeltaCore" -l"NESDeltaCore" -l"Roxas" -l"SDWebImage" -l"SMCalloutView" -l"SNESDeltaCore" -l"SQLite.swift" -l"SwiftyDropbox" -l"ZIPFoundation" -l"c++" -l"sqlite3" -l"z" -framework "CoreGraphics" -framework "CoreText" -framework "Crashlytics" -framework "Fabric" -framework "Foundation" -framework "GoogleSignIn" -framework "ImageIO" -framework "LocalAuthentication" -framework "SafariServices" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/DSDeltaCore/DSDeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/DeltaCore/DeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/GBADeltaCore/GBADeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/GBCDeltaCore/GBCDeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Harmony/Harmony.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/MelonDSDeltaCore/MelonDSDeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/N64DeltaCore/N64DeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/NESDeltaCore/NESDeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SNESDeltaCore/SNESDeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SwiftyDropbox/SwiftyDropbox.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation/ZIPFoundation.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/GTMSessionFetcher/GTMSessionFetcher.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/GoogleAPIClientForREST/GoogleAPIClientForREST.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/GoogleToolboxForMac/GoogleToolboxForMac.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/Roxas/Roxas.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/SDWebImage/SDWebImage.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/SMCalloutView/SMCalloutView.modulemap"

View File

@ -4,7 +4,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS" "${PODS_ROOT}/GoogleSignIn/Frameworks"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 STATIC_LIBRARY=1 STATIC_LIBRARY=1 JIT_ENABLED=1 STATIC_LIBRARY=1 MUPENPLUSAPI TXFILTER_LIB OS_IOS GLESX GL_ERROR_DEBUG GL_DEBUG GLESX PNG_ARM_NEON_OPT=0
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DSDeltaCore" "${PODS_ROOT}/Headers/Public/DeltaCore" "${PODS_ROOT}/Headers/Public/GBADeltaCore" "${PODS_ROOT}/Headers/Public/GBCDeltaCore" "${PODS_ROOT}/Headers/Public/GTMSessionFetcher" "${PODS_ROOT}/Headers/Public/GoogleAPIClientForREST" "${PODS_ROOT}/Headers/Public/GoogleToolboxForMac" "${PODS_ROOT}/Headers/Public/Harmony" "${PODS_ROOT}/Headers/Public/MelonDSDeltaCore" "${PODS_ROOT}/Headers/Public/N64DeltaCore" "${PODS_ROOT}/Headers/Public/NESDeltaCore" "${PODS_ROOT}/Headers/Public/Roxas" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/SMCalloutView" "${PODS_ROOT}/Headers/Public/SNESDeltaCore" "${PODS_ROOT}/Headers/Public/SQLite.swift" "${PODS_ROOT}/Headers/Public/SwiftyDropbox" "${PODS_CONFIGURATION_BUILD_DIR}" "$(PODS_ROOT)/Headers/Private/DSDeltaCore/desmume/desmume/src/libretro-common/include" "${PODS_CONFIGURATION_BUILD_DIR}" "$(PODS_ROOT)/Headers/Private/GBADeltaCore/SFML/include" "$(PODS_ROOT)/Headers/Private/GBADeltaCore/SFML/src" "${PODS_CONFIGURATION_BUILD_DIR}" "${PODS_CONFIGURATION_BUILD_DIR}" "$(PODS_ROOT)/Headers/Private/MelonDSDeltaCore/melonDS/src" "${PODS_CONFIGURATION_BUILD_DIR}" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/mupen64plus-core/subprojects/**" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/libMupen64Plus/SDL" "${PODS_CONFIGURATION_BUILD_DIR}" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/mupen64plus-core/subprojects/**" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/libMupen64Plus/SDL" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/GLideN64/src" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/GLideN64/src/inc" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/GLideN64/src" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/GLideN64/src/osal" "$(PODS_ROOT)/Headers/Private/N64DeltaCore/Mupen64Plus/libpng"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DSDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/DeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GBADeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GBCDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleToolboxForMac" "${PODS_CONFIGURATION_BUILD_DIR}/Harmony" "${PODS_CONFIGURATION_BUILD_DIR}/MelonDSDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/N64DeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/NESDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/Roxas" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SMCalloutView" "${PODS_CONFIGURATION_BUILD_DIR}/SNESDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyDropbox" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DSDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/DeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GBADeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GBCDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleToolboxForMac" "${PODS_CONFIGURATION_BUILD_DIR}/Harmony" "${PODS_CONFIGURATION_BUILD_DIR}/MelonDSDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/N64DeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/NESDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/Roxas" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SMCalloutView" "${PODS_CONFIGURATION_BUILD_DIR}/SNESDeltaCore" "${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyDropbox" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation" /usr/lib/swift
OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/DSDeltaCore/DSDeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/DeltaCore/DeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/GBADeltaCore/GBADeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/GBCDeltaCore/GBCDeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Harmony/Harmony.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/MelonDSDeltaCore/MelonDSDeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/N64DeltaCore/N64DeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/NESDeltaCore/NESDeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SNESDeltaCore/SNESDeltaCore.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SwiftyDropbox/SwiftyDropbox.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation/ZIPFoundation.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/GTMSessionFetcher/GTMSessionFetcher.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/GoogleAPIClientForREST/GoogleAPIClientForREST.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/GoogleToolboxForMac/GoogleToolboxForMac.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/Roxas/Roxas.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/SDWebImage/SDWebImage.modulemap" -fmodule-map-file="${PODS_ROOT}/Headers/Public/SMCalloutView/SMCalloutView.modulemap" -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/DSDeltaCore" -isystem "${PODS_ROOT}/Headers/Public/GBADeltaCore" -isystem "${PODS_ROOT}/Headers/Public/GBCDeltaCore" -isystem "${PODS_ROOT}/Headers/Public/Harmony" -isystem "${PODS_ROOT}/Headers/Public/MelonDSDeltaCore" -isystem "${PODS_ROOT}/Headers/Public/N64DeltaCore" -isystem "${PODS_ROOT}/Headers/Public/NESDeltaCore" -isystem "${PODS_ROOT}/Headers/Public/SNESDeltaCore" -iframework "${PODS_ROOT}/Crashlytics/iOS" -iframework "${PODS_ROOT}/Fabric/iOS" -iframework "${PODS_ROOT}/GoogleSignIn/Frameworks" -DHOST_DARWIN -DDESMUME_COCOA -DHAVE_OPENGL -DHAVE_LIBZ -DANDROID -fexceptions -ftree-vectorize -DCOMPRESS_MT -DIOS -DOBJ_C -marm -fvisibility=hidden -DSTATIC_LIBRARY=1 -DSTATIC_LIBRARY -DHAVE_CSTDINT -fno-strict-aliasing -DGCC -pthread -fPIC -D__unix__ -ffast-math -fno-strict-aliasing -DGCC -pthread -fPIC -D__unix__ -ffast-math -D__VEC4_OPT -fvisibility=hidden -funsigned-char -ffast-math -finline -fno-builtin -fno-common -fomit-frame-pointer -funroll-loops -fstrict-aliasing -DHAVE_STDINT_H -DARM -DSTATIC_LIBRARY=1
OTHER_LDFLAGS = $(inherited) -ObjC -Wl,-exported_symbol,_Video_PluginStartup,-exported_symbol,_Video_PluginShutdown,-exported_symbol,_Video_PluginGetVersion,-exported_symbol,_Video_RomOpen,-exported_symbol,_Video_RomClosed,-exported_symbol,_ConfigGetSharedDataFilepath,-exported_symbol,_ConfigGetUserConfigPath,-exported_symbol,_ConfigGetUserCachePath,-exported_symbol,_ConfigGetUserDataPath,-exported_symbol,_ConfigOpenSection,-exported_symbol,_ConfigDeleteSection,-exported_symbol,_ConfigSaveSection,-exported_symbol,_ConfigSaveFile,-exported_symbol,_ConfigSetDefaultInt,-exported_symbol,_ConfigSetDefaultFloat,-exported_symbol,_ConfigSetDefaultBool,-exported_symbol,_ConfigSetDefaultString,-exported_symbol,_ConfigGetParamInt,-exported_symbol,_ConfigGetParamFloat,-exported_symbol,_ConfigGetParamBool,-exported_symbol,_ConfigGetParamString,-exported_symbol,_ConfigExternalGetParameter,-exported_symbol,_ConfigExternalOpen,-exported_symbol,_ConfigExternalClose,-exported_symbol,_VidExt_Init,-exported_symbol,_VidExt_Quit,-exported_symbol,_VidExt_ListFullscreenModes,-exported_symbol,_VidExt_SetVideoMode,-exported_symbol,_VidExt_SetCaption,-exported_symbol,_VidExt_ToggleFullScreen,-exported_symbol,_VidExt_ResizeWindow,-exported_symbol,_VidExt_GL_GetProcAddress,-exported_symbol,_VidExt_GL_SetAttribute,-exported_symbol,_VidExt_GL_GetAttribute,-exported_symbol,_VidExt_GL_SwapBuffers,-exported_symbol,_ChangeWindow,-exported_symbol,_InitiateGFX,-exported_symbol,_MoveScreen,-exported_symbol,_ProcessDList,-exported_symbol,_ProcessRDPList,-exported_symbol,_ShowCFB,-exported_symbol,_UpdateScreen,-exported_symbol,_ViStatusChanged,-exported_symbol,_ViWidthChanged,-exported_symbol,_ReadScreen2,-exported_symbol,_SetRenderingCallback,-exported_symbol,_FBRead,-exported_symbol,_FBWrite,-exported_symbol,_FBGetFrameBufferInfo,-exported_symbol,_ResizeVideoOutput,-exported_symbol,_RSP_PluginStartup,-exported_symbol,_RSP_PluginShutdown,-exported_symbol,_RSP_PluginGetVersion,-exported_symbol,_DoRspCycles,-exported_symbol,_InitiateRSP,-exported_symbol,_RSP_RomClosed,-exported_symbol,_CoreGetAPIVersions,-exported_symbol,_ConfigGetParameter,-exported_symbol,_ConfigSetParameter,-exported_symbol,_CoreDoCommand -l"Alamofire" -l"DSDeltaCore" -l"GBADeltaCore" -l"GBCDeltaCore" -l"GTMSessionFetcher" -l"GoogleAPIClientForREST" -l"GoogleToolboxForMac" -l"Harmony" -l"MelonDSDeltaCore" -l"N64DeltaCore" -l"NESDeltaCore" -l"Roxas" -l"SDWebImage" -l"SMCalloutView" -l"SNESDeltaCore" -l"SQLite.swift" -l"SwiftyDropbox" -l"ZIPFoundation" -l"c++" -l"sqlite3" -l"z" -framework "CoreGraphics" -framework "CoreText" -framework "Crashlytics" -framework "Fabric" -framework "Foundation" -framework "GoogleSignIn" -framework "ImageIO" -framework "LocalAuthentication" -framework "SafariServices" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/DSDeltaCore/DSDeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/DeltaCore/DeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/GBADeltaCore/GBADeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/GBCDeltaCore/GBCDeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Harmony/Harmony.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/MelonDSDeltaCore/MelonDSDeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/N64DeltaCore/N64DeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/NESDeltaCore/NESDeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SNESDeltaCore/SNESDeltaCore.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SwiftyDropbox/SwiftyDropbox.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation/ZIPFoundation.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/GTMSessionFetcher/GTMSessionFetcher.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/GoogleAPIClientForREST/GoogleAPIClientForREST.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/GoogleToolboxForMac/GoogleToolboxForMac.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/Roxas/Roxas.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/SDWebImage/SDWebImage.modulemap" -Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/SMCalloutView/SMCalloutView.modulemap"

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>name</key>
<string>Grant Gliner</string>
<key>link</key>
<string>https://twitter.com/GrantGliner</string>
<key>linkName</key>
<string>@GrantGliner</string>
<key>contributions</key>
<array>
<dict>
<key>name</key>
<string>Pause Menu Icons</string>
</dict>
</array>
</dict>
<dict>
<key>name</key>
<string>Eric Lewis</string>
<key>link</key>
<string>https://github.com/ericlewis</string>
<key>linkName</key>
<string>github.com/ericlewis</string>
<key>contributions</key>
<array>
<dict>
<key>name</key>
<string>Dark Mode</string>
<key>link</key>
<string>https://github.com/rileytestut/Delta/pull/82</string>
</dict>
</array>
</dict>
<dict>
<key>name</key>
<string>Chris Rittenhouse</string>
<key>link</key>
<string>https://www.litritt.com</string>
<key>linkName</key>
<string>litritt.com</string>
<key>contributions</key>
<array>
<dict>
<key>name</key>
<string>Genesis Skin</string>
</dict>
</array>
</dict>
<dict>
<key>name</key>
<string>Noah Keck</string>
<key>link</key>
<string>https://github.com/noah978</string>
<key>linkName</key>
<string>github.com/noah978</string>
<key>contributions</key>
<array>
<dict>
<key>name</key>
<string>CheatBase</string>
<key>link</key>
<string>https://github.com/CheatBase/CheatBase</string>
</dict>
</array>
</dict>
</array>
</plist>

BIN
Resources/cheatbase.zip Normal file

Binary file not shown.

BIN
Resources/openvgdb.sqlite Executable file → Normal file

Binary file not shown.