diff --git a/GC Manager/GCMConstants.h b/GC Manager/GCMConstants.h index 4e2bdcd..2de44f5 100644 --- a/GC Manager/GCMConstants.h +++ b/GC Manager/GCMConstants.h @@ -9,13 +9,6 @@ #ifndef GCMConstants_h #define GCMConstants_h -#if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted) - #define GCM_FINAL __attribute__((objc_subclassing_restricted)) -#else - #define GCM_FINAL -#endif - - /// Leaderboard sort order. Use this value when submitting new leaderboard scores. This value should match the value set in iTunes Connect for the speicifed leaderboard. typedef enum GameCenterSortOrder { /// Scores are sorted highest to lowest. Higher scores are on the top of the leaderboard diff --git a/GC Manager/GCMMultiplayer.h b/GC Manager/GCMMultiplayer.h index 56d568f..46c7112 100644 --- a/GC Manager/GCMMultiplayer.h +++ b/GC Manager/GCMMultiplayer.h @@ -13,7 +13,7 @@ @protocol GameCenterMultiplayerManagerDelegate; -GCM_FINAL @interface GCMMultiplayer : NSObject +@interface GCMMultiplayer : NSObject /// Returns the default instance of the multiplayer manager + (GCMMultiplayer *)defaultMultiplayerManager; @@ -74,7 +74,12 @@ GCM_FINAL @interface GCMMultiplayer : NSObject +// Allows for NSData conversion of GKLeaderboardScore (Basically Serialisation / Copy Ctor) + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface GKLeaderboardScore (NSCoder) + +@property(class, readonly) BOOL supportsSecureCoding; + +//@property (strong, nonatomic) GKPlayer *player; +//@property (assign, nonatomic) NSInteger value; +//@property (assign, nonatomic) NSUInteger context; +//@property (strong, nonatomic) NSString *leaderboardID; + +- (void) encodeWithCoder: (NSCoder *)coder; + +- (id) initWithCoder: (NSCoder *)coder; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GC Manager/GKLeaderboardScore+NSCoder.m b/GC Manager/GKLeaderboardScore+NSCoder.m new file mode 100644 index 0000000..d623448 --- /dev/null +++ b/GC Manager/GKLeaderboardScore+NSCoder.m @@ -0,0 +1,41 @@ +// +// GKLeaderboardScore+NSCoder.h +// Super Hexagon +// +// Created by Daniel Rosser for Super Hexagon on 24/8/2022 + + +#import +#import "GKLeaderboardScore+NSCoder.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation GKLeaderboardScore (NSCoder) + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [coder encodeInt64:self.value forKey:@"value"]; + [coder encodeInt64:self.context forKey:@"context"]; + [coder encodeObject:self.leaderboardID forKey:@"leaderboardID"]; + [coder encodeObject:self.player forKey:@"player"]; +} + +- (id) initWithCoder: (NSCoder *)coder +{ + if (self = [super init]) + { + self.value = [coder decodeInt64ForKey:@"value"]; + self.context = [coder decodeInt64ForKey:@"context"]; + self.leaderboardID = [coder decodeObjectForKey:@"leaderboardID"]; + self.player = [coder decodeObjectForKey:@"player"]; + } + return self; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/GC Manager/GameCenterManager.h b/GC Manager/GameCenterManager.h old mode 100755 new mode 100644 index 46c098b..fa16d5d --- a/GC Manager/GameCenterManager.h +++ b/GC Manager/GameCenterManager.h @@ -1,24 +1,13 @@ // // GameCenterManager.h // + // Created by Nihal Ahmed on 12-03-16. Updated by iRare Media on 7/2/13. // Copyright (c) 2012 NABZ Software. All rights reserved. -// +// Updated by Daniel Rosser for Super Hexagon on 19/7/22 #include -// As of version 5.3, GameCenterManager only runs on iOS 7.0+ and OS X 10.9+, check for compatibility before building. See the GitHub Releases page (https://github.com/nihalahmed/GameCenterManager/releases) for older versions which work with iOS 4.1 and higher and OS X 10.8 and higher. The last supported version for iOS < 7.0 is version 5.2. The last supported version for OS X < 10.9 is also version 5.2. -#if TARGET_OS_IOS || (TARGET_OS_IPHONE && !TARGET_OS_TV) - #ifndef __IPHONE_7_0 - #warning GameCenterManager uses features only available in iOS SDK 7.0 and later. Running on an older version of iOS may result in a crash. Download an older release from GitHub for compatibility with iOS SDK < 7.0 - #endif -#elif TARGET_OS_TV - // -#else - #ifndef __MAC_10_9 - #warning GameCenterManager uses features only available in OS X SDK 10.9 and later. Running on an older version of OS X may result in a crash. Download an older release from GitHub for compatibility with OS X SDK < 10.9 - #endif -#endif #if TARGET_OS_IOS || (TARGET_OS_IPHONE && !TARGET_OS_TV) #define kApplicationAppSupportDirectory [NSHomeDirectory() stringByAppendingPathComponent:@"Library"] @@ -35,6 +24,8 @@ #import #import +#import "NSUserDefaults+MPSecureUserDefaults.h" +#import "GKLeaderboardScore+NSCoder.h" #if TARGET_OS_IPHONE #import @@ -54,7 +45,7 @@ /// GameCenter Manager helps to manage Game Center in iOS and Mac apps. Report and keep track of high scores, achievements, and challenges for different players. GameCenter Manager also takes care of the heavy lifting - checking internet availability, saving data when offline and uploading it when online, etc. @class GameCenterManager; @protocol GameCenterManagerDelegate; -GCM_FINAL @interface GameCenterManager : NSObject +@interface GameCenterManager : NSObject /// Returns the shared instance of GameCenterManager. @@ -69,11 +60,6 @@ GCM_FINAL @interface GameCenterManager : NSObject *)leaderboardIDs; + /// Synchronizes local player data with Game Center data. - (void)syncGameCenter; +- (void)forceSyncGameCenter; + +- (void)settingReportScores:(BOOL)sendAll; + +- (void)clearTopScores; + +- (void)logout; + /** Saves score locally and reports it to Game Center. If error occurs, score is saved to be submitted later. @param score The long long value of the score to be submitted to Game Center. This score should not be formatted, instead it should be a plain long long (int). For example, if you wanted to submit a score of 45.28 meters then you would submit it as an integer of 4528. To format your scores, you must set the Score Formatter for your leaderboard in iTunes Connect. @param identifier The Leaderboard ID set through iTunes Connect. This is different from the name of the leaderboard, and it is not shown to the user. @param order The score sort order that you set in iTunes Connect - either high to low or low to high. This is used to determine if the user has a new highscore before submitting. */ -- (void)saveAndReportScore:(long long)score leaderboard:(NSString *)identifier sortOrder:(GameCenterSortOrder)order __attribute__((nonnull)); +- (void)saveAndReportScore:(long long)score leaderboard:(NSString *)identifier sortOrder:(GameCenterSortOrder)order __attribute__((nonnull)) __deprecated; + +- (void)saveAndReportScore:(long long)score context:(long long)context leaderboard:(NSString *)identifier sortOrder:(GameCenterSortOrder)order __attribute__((nonnull)); /** Saves achievement locally and reports it to Game Center. If error occurs, achievement is saved to be submitted later. @@ -104,10 +103,14 @@ GCM_FINAL @interface GameCenterManager : NSObject // GameCenterManager uses ARC, check for compatibility before building #if !__has_feature(objc_arc) @@ -17,19 +17,26 @@ //------------------------------------------------------------------------------------------------------------// #pragma mark GameCenter Manager -#define IS_IOS_8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) @interface GameCenterManager () { NSMutableArray *GCMLeaderboards; + int leaderboardIndex; #if TARGET_OS_IPHONE UIBackgroundTaskIdentifier backgroundProcess; + #endif } +@property (nonatomic, strong, readwrite) NSArray*gameleaderboardIDs; +@property (nonatomic, assign, readwrite) BOOL hasLeaderboards; +@property (nonatomic, assign, readwrite) BOOL sendEveryScore; @property (nonatomic, assign, readwrite) BOOL shouldCryptData; +@property (nonatomic, assign, readwrite) BOOL shouldCryptNSData; +@property (nonatomic, assign, readwrite) BOOL useNSDefaults; @property (nonatomic, strong, readwrite) NSString *cryptKey; @property (nonatomic, strong, readwrite) NSData *cryptKeyData; +@property (nonatomic, strong, readwrite) NSString * lastPlayerID; @property (nonatomic, assign, readwrite) GameCenterAvailability previousGameCenterAvailability; @end @@ -51,6 +58,101 @@ + (GameCenterManager *)sharedManager { - (instancetype)init { self = [super init]; + + self->leaderboardIndex = 0; + self.lastPlayerID = [[[NSUserDefaults standardUserDefaults] stringForKey:@"lastKnownID"] mutableCopy]; + if(self.lastPlayerID == nil) { + self.lastPlayerID = @"unknownPlayer"; + } + return self; +} + +//------------------------------------------------------------------------------------------------------------// +//------- GameCenter Manager Setup ---------------------------------------------------------------------------// +//------------------------------------------------------------------------------------------------------------// +#pragma mark - GameCenter Manager Setup + +- (void)logout { + + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setBool:NO forKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]; + [userDefaults setBool:NO forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + [userDefaults setObject:nil forKey:@"lastKnownID"]; + + [userDefaults synchronize]; + + NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier]; + [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain]; + + NSLog(@"Game Center Manager - Logout - Values Reset"); + + +} + +- (void)setupManagerWithLeaderboardIDs:(NSArray*)leaderboardIDs { + _gameleaderboardIDs = leaderboardIDs; + if(_gameleaderboardIDs != NULL && _gameleaderboardIDs.count > 0) { + _hasLeaderboards = YES; + } else { + _hasLeaderboards = NO; + } + + leaderboardIndex = 0; + +} + +- (void)setupManager { + + // NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + // [userDefaults setBool:NO forKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]; + // [userDefaults setBool:NO forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + // [userDefaults synchronize]; + + // This code should only be called once, to avoid unhandled exceptions when parsing the PLIST data +#if !TARGET_OS_TV + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [self setShouldCryptData:YES]; + + if(self.useNSDefaults == NO) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + if(![fileManager fileExistsAtPath:kApplicationAppSupportDirectory]) { + NSError *error = nil; + BOOL isDirectoryCreated = [fileManager createDirectoryAtPath:kApplicationAppSupportDirectory withIntermediateDirectories:NO attributes:nil error:&error]; + if(!isDirectoryCreated) NSLog(@"Failed to created Application Support Folder: %@", error); + } + + if (![fileManager fileExistsAtPath:kGameCenterManagerDataPath]) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + NSError *error = nil; + NSData *saveData = nil; + if(self.shouldCryptData) + saveData = [NSKeyedArchiver archivedDataWithRootObject:dict requiringSecureCoding:self.shouldCryptNSData error:&error]; + else + saveData = [NSKeyedArchiver archivedDataWithRootObject:dict requiringSecureCoding:self.shouldCryptNSData error:&error]; + + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + + NSData *gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + if (gameCenterManagerData == nil) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + NSError *error = nil; + + NSData *saveData = nil; + if(self.shouldCryptData) + saveData = [NSKeyedArchiver archivedDataWithRootObject:dict requiringSecureCoding:self.shouldCryptNSData error:&error]; + else + saveData = [NSKeyedArchiver archivedDataWithRootObject:dict requiringSecureCoding:self.shouldCryptNSData error:&error]; + + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + } + }); +#else + // tvOS doesn't need to initialize any plist file as it must use NSUserDefaults + self.useNSDefaults = YES; +#endif if (self) { BOOL gameCenterAvailable = [self checkGameCenterAvailability:YES]; @@ -66,7 +168,7 @@ - (instancetype)init { if ([prefs objectForKey:@"achievementsSynced"] == nil) { NSLog(@"achievementsSynced not setup"); - [prefs setBool:NO forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + [prefs setBool:NO forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; } else { NSLog(@"achievementsSynced WAS setup"); } @@ -76,62 +178,20 @@ - (instancetype)init { if (gameCenterAvailable) { // Set GameCenter as available [self setIsGameCenterAvailable:YES]; - + if (![[NSUserDefaults standardUserDefaults] boolForKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]] || ![[NSUserDefaults standardUserDefaults] boolForKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]) [self syncGameCenter]; else - [self reportSavedScoresAndAchievements]; + if (@available(iOS 14.0, *)) + [self reportSavedLeaderboardScoresAndAchievements]; + else + [self reportSavedScoresAndAchievements]; } else { [self setIsGameCenterAvailable:NO]; } } - return self; -} - -//------------------------------------------------------------------------------------------------------------// -//------- GameCenter Manager Setup ---------------------------------------------------------------------------// -//------------------------------------------------------------------------------------------------------------// -#pragma mark - GameCenter Manager Setup - -- (void)initGameCenter { - for (int i = 0; i == 3; i++) { - NSLog(@"WARNING: Calling a deprecated GameCenterManager method that may become obsolete in future versions. This method no longer has any function. Use setupManager instead of initGameCenter. %s", __PRETTY_FUNCTION__); - } -} - -- (void)setupManager { - // This code should only be called once, to avoid unhandled exceptions when parsing the PLIST data -#if !TARGET_OS_TV - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [self setShouldCryptData:NO]; - - NSFileManager *fileManager = [NSFileManager defaultManager]; - if(![fileManager fileExistsAtPath:kApplicationAppSupportDirectory]) { - NSError *error = nil; - BOOL isDirectoryCreated = [fileManager createDirectoryAtPath:kApplicationAppSupportDirectory withIntermediateDirectories:NO attributes:nil error:&error]; - if(!isDirectoryCreated) NSLog(@"Failed to created Application Support Folder: %@", error); - } - - if (![fileManager fileExistsAtPath:kGameCenterManagerDataPath]) { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - NSData *saveData = [NSKeyedArchiver archivedDataWithRootObject:dict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; - } - - NSData *gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - if (gameCenterManagerData == nil) { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - NSData *saveData = [NSKeyedArchiver archivedDataWithRootObject:dict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; - } - }); -#else - // tvOS doesn't need to initialize any plist file as it must use NSUserDefaults -#endif - } - (void)setupManagerAndSetShouldCryptWithKey:(NSString *)cryptionKey { @@ -140,40 +200,51 @@ - (void)setupManagerAndSetShouldCryptWithKey:(NSString *)cryptionKey { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ self.shouldCryptData = YES; + self.shouldCryptNSData = NO; + self.useNSDefaults = YES; self.cryptKey = cryptionKey; self.cryptKeyData = [cryptionKey dataUsingEncoding:NSUTF8StringEncoding]; - - NSFileManager *fileManager = [NSFileManager defaultManager]; - if(![fileManager fileExistsAtPath:kApplicationAppSupportDirectory]) { - NSError *error = nil; - BOOL isDirectoryCreated = [fileManager createDirectoryAtPath:kApplicationAppSupportDirectory withIntermediateDirectories:NO attributes:nil error:&error]; - if(!isDirectoryCreated) NSLog(@"Failed to created Application Support Folder: %@", error); - } - - if (![fileManager fileExistsAtPath:kGameCenterManagerDataPath]) { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:dict] encryptedWithKey:self.cryptKeyData]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; - } - - NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - if (gameCenterManagerData == nil) { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:dict] encryptedWithKey:self.cryptKeyData]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + [NSUserDefaults setSecret:cryptionKey]; + if(self.useNSDefaults == NO) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + if(![fileManager fileExistsAtPath:kApplicationAppSupportDirectory]) { + NSError *error = nil; + BOOL isDirectoryCreated = [fileManager createDirectoryAtPath:kApplicationAppSupportDirectory withIntermediateDirectories:NO attributes:nil error:&error]; + if(!isDirectoryCreated) NSLog(@"Failed to created Application Support Folder: %@", error); + } + + if (![fileManager fileExistsAtPath:kGameCenterManagerDataPath]) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + // NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:dict] encryptedWithKey:self.cryptKeyData]; + + NSError *error = nil; + NSData *saveData = nil; + if(self.shouldCryptData) + saveData = [NSKeyedArchiver archivedDataWithRootObject:dict requiringSecureCoding:self.shouldCryptNSData error:&error]; + else + saveData = [NSKeyedArchiver archivedDataWithRootObject:dict requiringSecureCoding:self.shouldCryptNSData error:&error]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; + if (gameCenterManagerData == nil) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + NSError *error = nil; + NSData *saveData = nil; + if(self.shouldCryptData) + saveData = [NSKeyedArchiver archivedDataWithRootObject:dict requiringSecureCoding:self.shouldCryptNSData error:&error]; + else + saveData = [NSKeyedArchiver archivedDataWithRootObject:dict requiringSecureCoding:self.shouldCryptNSData error:&error]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } } }); #else // tvOS doesn't need to initialize any plist file as it must use NSUserDefaults + self.useNSDefaults = YES; #endif } -- (BOOL)checkGameCenterAvailability { - // left here for backwards compatibility. Because previous versions of GameCenterManager were built with without the ignorePreviousState feature, we will preserve the old - NSLog(@"WARNING: Calling a deprecated GameCenterManager method that may become obsolete in future versions. Use checkGameCenterAvailability: ignorePreviousStatus: instead. %s", __PRETTY_FUNCTION__); - return [self checkGameCenterAvailability:YES]; -} - - (BOOL)checkGameCenterAvailability:(BOOL)ignorePreviousStatus { #if TARGET_OS_IPHONE // First, check if the the GameKit Framework exists on the device. Return NO if it does not. @@ -206,7 +277,7 @@ - (BOOL)checkGameCenterAvailability:(BOOL)ignorePreviousStatus { if ([self previousGameCenterAvailability] != GameCenterAvailabilityNoInternet) { [self setPreviousGameCenterAvailability:GameCenterAvailabilityNoInternet]; NSDictionary *errorDictionary = @{@"message": @"Cannot connect to the internet. Connect to the internet to establish a connection with GameCenter. Achievements and scores will still be saved locally and then uploaded later.", @"title": @"Internet Unavailable"}; - + dispatch_async(dispatch_get_main_queue(), ^{ if ([[self delegate] respondsToSelector:@selector(gameCenterManager:availabilityChanged:)]) [[self delegate] gameCenterManager:self availabilityChanged:errorDictionary]; @@ -217,59 +288,11 @@ - (BOOL)checkGameCenterAvailability:(BOOL)ignorePreviousStatus { } else { // The internet is available and the current device is connected. Now check if the player is authenticated - GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; -#if TARGET_OS_IPHONE - localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error) { - if (viewController != nil) { - if ([self previousGameCenterAvailability] != GameCenterAvailabilityNoPlayer) { - [self setPreviousGameCenterAvailability:GameCenterAvailabilityNoPlayer]; - NSDictionary *errorDictionary = @{@"message": @"Player is not yet signed into GameCenter. Please prompt the player using the authenticateUser delegate method.", @"title": @"No Player"}; - dispatch_async(dispatch_get_main_queue(), ^{ - if ([[self delegate] respondsToSelector:@selector(gameCenterManager:availabilityChanged:)]) - [[self delegate] gameCenterManager:self availabilityChanged:errorDictionary]; - - if ([[self delegate] respondsToSelector:@selector(gameCenterManager:authenticateUser:)]) { - [[self delegate] gameCenterManager:self authenticateUser:viewController]; - } else { - NSLog(@"[ERROR] %@ Fails to Respond to the required delegate method gameCenterManager:authenticateUser:. This delegate method must be properly implemented to use GC Manager", [self delegate]); - } - }); - } - } else if (!error) { - // Authentication handler completed successfully. Re-check availability - [self checkGameCenterAvailability:ignorePreviousStatus]; - } - }; -#else - localPlayer.authenticateHandler = ^(NSViewController *viewController, NSError *error) { - if (viewController != nil) { - if ([self previousGameCenterAvailability] != GameCenterAvailabilityNoPlayer) { - [self setPreviousGameCenterAvailability:GameCenterAvailabilityNoPlayer]; - NSDictionary *errorDictionary = @{@"message": @"Player is not yet signed into GameCenter. Please prompt the player using the authenticateUser delegate method.", @"title": @"No Player"}; - - dispatch_async(dispatch_get_main_queue(), ^{ - if ([[self delegate] respondsToSelector:@selector(gameCenterManager:availabilityChanged:)]) - [[self delegate] gameCenterManager:self availabilityChanged:errorDictionary]; - - if ([[self delegate] respondsToSelector:@selector(gameCenterManager:authenticateUser:)]) { - [[self delegate] gameCenterManager:self authenticateUser:viewController]; - } else { - NSLog(@"[ERROR] %@ Fails to Respond to the required delegate method gameCenterManager:authenticateUser:. This delegate method must be properly implemented to use GC Manager", [self delegate]); - } - }); - } - } else if (!error) { - // Authentication handler completed successfully. Re-check availability - [self checkGameCenterAvailability:ignorePreviousStatus]; - } - }; -#endif - if (![[GKLocalPlayer localPlayer] isAuthenticated]) { if ([self previousGameCenterAvailability] != GameCenterAvailabilityPlayerNotAuthenticated) { [self setPreviousGameCenterAvailability:GameCenterAvailabilityPlayerNotAuthenticated]; NSDictionary *errorDictionary = @{@"message": @"Player is not signed into GameCenter, has declined to sign into GameCenter, or GameKit had an issue validating this game / app.", @"title": @"Player not Authenticated"}; - + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:availabilityChanged:)]) [[self delegate] gameCenterManager:self availabilityChanged:errorDictionary]; } @@ -280,17 +303,22 @@ - (BOOL)checkGameCenterAvailability:(BOOL)ignorePreviousStatus { if ([self previousGameCenterAvailability] != GameCenterAvailabilityPlayerAuthenticated) { [self setPreviousGameCenterAvailability:GameCenterAvailabilityPlayerAuthenticated]; // The current player is logged into GameCenter + + self.isGameCenterAvailable = YES; NSDictionary *successDictionary = [NSDictionary dictionaryWithObject:@"GameCenter Available" forKey:@"status"]; NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setBool:NO forKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]; [userDefaults setBool:NO forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + + [userDefaults setObject:[self localPlayerId] forKey:@"lastKnownID"]; + self.lastPlayerID = [self localPlayerId]; [userDefaults synchronize]; + dispatch_async(dispatch_get_main_queue(), ^{ if ([[self delegate] respondsToSelector:@selector(gameCenterManager:availabilityChanged:)]) [[self delegate] gameCenterManager:self availabilityChanged:successDictionary]; }); - - self.isGameCenterAvailable = YES; } return YES; @@ -299,6 +327,88 @@ - (BOOL)checkGameCenterAvailability:(BOOL)ignorePreviousStatus { } } +- (BOOL)authenticateUser { + GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; +#if TARGET_OS_IPHONE + if(!localPlayer.isAuthenticated && localPlayer.authenticateHandler == nil) { + localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error) { + // if (viewController != nil) { + if ([self previousGameCenterAvailability] != GameCenterAvailabilityNoPlayer) { + [self setPreviousGameCenterAvailability:GameCenterAvailabilityNoPlayer]; + NSDictionary *errorDictionary = @{@"message": @"Player is not yet signed into GameCenter. Please prompt the player using the authenticateUser delegate method.", @"title": @"No Player"}; + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:availabilityChanged:)]) + [[self delegate] gameCenterManager:self availabilityChanged:errorDictionary]; + + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:authenticateUser:)]) { + [[self delegate] gameCenterManager:self authenticateUser:viewController]; + } else { + NSLog(@"[ERROR] %@ Fails to Respond to the required delegate method gameCenterManager:authenticateUser:. This delegate method must be properly implemented to use GC Manager", [self delegate]); + } + }); + // } + } else if (!error) { + // Authentication handler completed successfully. Re-check availability + [self checkGameCenterAvailability:YES]; + } + }; + } else if(!localPlayer.isAuthenticated && localPlayer.authenticateHandler != nil) { + NSLog(@"GameCenter not authenticated ... already showed authview ... taking to login directly"); + NSURL * url = [NSURL URLWithString:@"gamecenter:/me/account"]; +// [[UIApplication sharedApplication] openURL:url]; + + [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:^(BOOL success) { + if(success == NO){ + + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:failedToAuthenticateUser:)]) { + [[self delegate] gameCenterManager:self failedToAuthenticateUser:NO]; + } else { + NSLog(@"[ERROR] %@ Fails to Respond to the required delegate method gameCenterManager:failedToAuthenticateUser:. This delegate method must be properly implemented to use GC Manager to support showing alert view prompt", [self delegate]); + + // UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Game Center Login Problem" + // message:@"Please login to game center in device settings or close and restart this app" + // preferredStyle:UIAlertControllerStyleAlert]; + // + // UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault + // handler:^(UIAlertAction * action) {}]; + // + // [alert addAction:defaultAction]; + // [self presentViewController:alert animated:YES completion:nil]; + } + + + + } + }]; + } +#else + if(!localPlayer.isAuthenticated) { + localPlayer.authenticateHandler = ^(NSViewController *viewController, NSError *error) { + if (viewController != nil) { + if ([self previousGameCenterAvailability] != GameCenterAvailabilityNoPlayer) { + [self setPreviousGameCenterAvailability:GameCenterAvailabilityNoPlayer]; + NSDictionary *errorDictionary = @{@"message": @"Player is not yet signed into GameCenter. Please prompt the player using the authenticateUser delegate method.", @"title": @"No Player"}; + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:availabilityChanged:)]) + [[self delegate] gameCenterManager:self availabilityChanged:errorDictionary]; + + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:authenticateUser:)]) { + [[self delegate] gameCenterManager:self authenticateUser:viewController]; + } else { + NSLog(@"[ERROR] %@ Fails to Respond to the required delegate method gameCenterManager:authenticateUser:. This delegate method must be properly implemented to use GC Manager", [self delegate]); + } + }); + } + } else if (!error) { + // Authentication handler completed successfully. Re-check availability + [self checkGameCenterAvailability:YES]; + } + }; + } +#endif +} + // Check for internet with Reachability - (BOOL)isInternetAvailable { Reachability *reachability = [Reachability reachabilityForInternetConnection]; @@ -322,132 +432,422 @@ - (BOOL)isInternetAvailable { //------------------------------------------------------------------------------------------------------------// //------- GameCenter Syncing ---------------------------------------------------------------------------------// //------------------------------------------------------------------------------------------------------------// +- (void)settingReportScores:(BOOL)sendAll { + self.sendEveryScore = sendAll; +} + +- (void)clearTopScores { + + if(!self.hasLeaderboards) return; + + NSMutableDictionary *playerDict; + + NSError * error; +#if !TARGET_OS_TV + NSMutableDictionary *plistDict = nil; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } + +#endif + NSUserDefaults *defaults = nil; + + if(self.useNSDefaults == YES) { + defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } + if (playerDict == nil) playerDict = [NSMutableDictionary dictionary]; + for(int i = 0; iGCMLeaderboards = nil; + if(self.useNSDefaults == YES) { + if(self.shouldCryptData) { + [defaults setSecureObject:playerDict forKey:[self localPlayerId]]; + } else { + [defaults setObject:playerDict forKey:[self localPlayerId]]; + } + [defaults setBool:NO forKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]; + [defaults setBool:NO forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + + [defaults synchronize]; + } +} + #pragma mark - GameCenter Syncing +- (void)forceSyncGameCenter { + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + self->GCMLeaderboards = nil; + if(self.useNSDefaults == YES) { + [defaults setBool:NO forKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]; + [defaults setBool:NO forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + + [defaults synchronize]; + } + [self syncGameCenter]; +} + - (void)syncGameCenter { #if TARGET_OS_IPHONE || TARGET_OS_TV + //NSLog(@"SyncGameCenter"); // Begin Syncing with GameCenter - - // Ensure the task isn't interrupted even if the user exits the app - backgroundProcess = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ - //End the Background Process - [[UIApplication sharedApplication] endBackgroundTask:backgroundProcess]; - backgroundProcess = UIBackgroundTaskInvalid; - }]; - // Move the process to the background thread to avoid clogging up the UI dispatch_queue_t syncGameCenterOnBackgroundThread = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul); dispatch_async(syncGameCenterOnBackgroundThread, ^{ + BOOL achievementsSynced = [[NSUserDefaults standardUserDefaults] boolForKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + BOOL scoresSynced = [[NSUserDefaults standardUserDefaults] boolForKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]; + // Check if GameCenter is available if ([self checkGameCenterAvailability:NO] == YES) { // Check if Leaderboard Scores are synced - if (![[NSUserDefaults standardUserDefaults] boolForKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]) { - if (GCMLeaderboards == nil) { - [GKLeaderboard loadLeaderboardsWithCompletionHandler:^(NSArray *leaderboards, NSError *error) { - if (error == nil) { - GCMLeaderboards = [[NSMutableArray alloc] initWithArray:leaderboards]; - [self syncGameCenter]; + if (self->_hasLeaderboards == YES && self->GCMLeaderboards == nil) { + if(self->_hasLeaderboards == YES) { + if (@available(iOS 14.0, *)) { + [GKLeaderboard loadLeaderboardsWithIDs:self->_gameleaderboardIDs completionHandler:^(NSArray * _Nullable leaderboards, NSError * _Nullable error) { + if (error == nil) { + self->GCMLeaderboards = [[NSMutableArray alloc] initWithArray:leaderboards]; + if(leaderboards.count > 0){ + self->leaderboardIndex = leaderboards.count -1; + + } + [self syncGameCenter]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; } else { - dispatch_async(dispatch_get_main_queue(), ^{ - if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) - [[self delegate] gameCenterManager:self error:error]; - }); + // Fallback on earlier versions + [GKLeaderboard loadLeaderboardsWithCompletionHandler:^(NSArray *leaderboards, NSError *error) { + if (error == nil) { + self->GCMLeaderboards = [[NSMutableArray alloc] initWithArray:leaderboards]; + + if(leaderboards.count > 0){ + self->leaderboardIndex = leaderboards.count -1; + + } + + [self syncGameCenter]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; } - }]; - return; - } + + return; + } + + } else if(!scoresSynced && self->_hasLeaderboards == YES) { + if (self->GCMLeaderboards.count > 0 && self->leaderboardIndex >= 0) { + + GKLeaderboard * leaderboardRequest; - if (GCMLeaderboards.count > 0) { + + if (@available(iOS 14.0, *)) { + + leaderboardRequest = (GKLeaderboard *)self->GCMLeaderboards[self->leaderboardIndex]; + [leaderboardRequest setIdentifier:[(GKLeaderboard *)[self->GCMLeaderboards objectAtIndex:self->leaderboardIndex] baseLeaderboardID]]; + + + [leaderboardRequest loadEntriesForPlayerScope:GKLeaderboardPlayerScopeGlobal + timeScope:GKLeaderboardTimeScopeAllTime range:NSMakeRange(1, 10) completionHandler:^(GKLeaderboardEntry * _Nullable_result localPlayerEntry, NSArray * _Nullable entries, NSInteger totalPlayerCount, NSError * _Nullable error) { + if (error == nil) { + if (entries.count > 0) { + NSMutableDictionary *playerDict; +#if !TARGET_OS_TV + NSMutableDictionary *plistDict; + if(self.useNSDefaults==NO) { + + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + if(error != nil) { + NSLog(@"GameCenterManager:: failed to load plistDict with error: %@", error.description); + error = nil; + } + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } +#endif + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + if (playerDict == nil) { + playerDict = [NSMutableDictionary dictionary]; + } + + float savedHighScoreValue = 0; + + NSNumber *savedHighScore = [playerDict objectForKey:leaderboardRequest.baseLeaderboardID]; + + if (savedHighScore != nil) { + savedHighScoreValue = [savedHighScore longLongValue]; + } + + + long long playerScore = localPlayerEntry != nil ? localPlayerEntry.score : 0; + [playerDict setObject:[NSNumber numberWithLongLong:MAX(playerScore, savedHighScoreValue)] forKey:leaderboardRequest.baseLeaderboardID]; +#if !TARGET_OS_TV + if(self.useNSDefaults==NO) { + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } +#else + +#endif + if(self.shouldCryptData) { + [defaults setSecureObject:playerDict forKey:[self localPlayerId]]; + } else { + [defaults setObject:playerDict forKey:[self localPlayerId]]; + } + [defaults synchronize]; + } + + // Seeing an NSRangeException for an empty array when trying to remove the object + // Despite the check above in this scope that leaderboards count is > 0 + if (self->GCMLeaderboards.count > 0 && self->leaderboardIndex >= 0) { + //[self->GCMLeaderboards removeObjectAtIndex:0]; + self->leaderboardIndex--; + } + + [self syncGameCenter]; + } + + }]; + } else { // < iOS 14 - GKLeaderboard *leaderboardRequest; - if(IS_IOS_8_OR_LATER) { leaderboardRequest = [[GKLeaderboard alloc] initWithPlayers:[NSArray arrayWithObject:[GKLocalPlayer localPlayer]]]; - } else { - #if TARGET_OS_IOS || (TARGET_OS_IPHONE && !TARGET_OS_TV) - leaderboardRequest = [[GKLeaderboard alloc] initWithPlayerIDs:[NSArray arrayWithObject:[self localPlayerId]]]; - #endif - } - [leaderboardRequest setIdentifier:[(GKLeaderboard *)[GCMLeaderboards objectAtIndex:0] identifier]]; - - [leaderboardRequest loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) { - if (error == nil) { - if (scores.count > 0) { - NSMutableDictionary *playerDict; - #if !TARGET_OS_TV + + [leaderboardRequest setIdentifier:[self->_gameleaderboardIDs objectAtIndex:self->leaderboardIndex]]; + // Fallback on earlier versions + [leaderboardRequest loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) { + if (error == nil) { + if (scores.count > 0) { + NSMutableDictionary *playerDict; + + GKScore * playerScore = nil; + for(int i= 0; iGCMLeaderboards != nil){ +// +// +// for(int i= 0; iGCMLeaderboards.count; i++) +// { +// GKLeaderboard * leaderboardRequest = (GKLeaderboard *)self->GCMLeaderboards[i]; +// +// if(leaderboardRequest == nil) { +// NSLog(@"GameCenterManager:: unable to cast"); +// continue; +// } +// +// if(self->GCMLeaderboards.count >= self->_gameleaderboardIDs.count) { +// [leaderboardRequest setIdentifier:[self->_gameleaderboardIDs objectAtIndex:i]]; +// } else { +// NSLog(@"GameCenterManager:: iOS 13 size of leaderboards issue"); +// continue; +// } +// + + + // if (scores.count > 0) { +// NSMutableDictionary *playerDict; +#if !TARGET_OS_TV NSMutableDictionary *plistDict; - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; - #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; - #endif + if(self.useNSDefaults==NO) { + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + + if(error != nil) { + NSLog(@"GameCenterManager:: failed to load plistDict with error: %@", error.description); + error = nil; + } + + + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } +#else + +#endif + NSUserDefaults *defaults = nil; + if(self.useNSDefaults==YES) { + defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } if (playerDict == nil) { playerDict = [NSMutableDictionary dictionary]; } - + NSString * leaderboardID = leaderboardRequest.identifier; float savedHighScoreValue = 0; - NSNumber *savedHighScore = [playerDict objectForKey:leaderboardRequest.localPlayerScore.leaderboardIdentifier]; + NSNumber *savedHighScore = [playerDict objectForKey:leaderboardRequest.identifier]; if (savedHighScore != nil) { savedHighScoreValue = [savedHighScore longLongValue]; } + long long leaderboardScore = playerScore != nil ? playerScore.value : 0; + [playerDict setObject:[NSNumber numberWithLongLong:MAX(leaderboardScore, savedHighScoreValue)] forKey:leaderboardID]; - [playerDict setObject:[NSNumber numberWithLongLong:MAX(leaderboardRequest.localPlayerScore.value, savedHighScoreValue)] forKey:leaderboardRequest.localPlayerScore.leaderboardIdentifier]; - #if !TARGET_OS_TV - [plistDict setObject:playerDict forKey:[self localPlayerId]]; - NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; - #else - [defaults setObject:playerDict forKey:[self localPlayerId]]; - [defaults synchronize]; - #endif + + +#if !TARGET_OS_TV + + if(self.useNSDefaults==NO) { + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + if(error != nil) { + NSLog(@"GameCenterManager:: failed to saveData with error: %@", error.description); + error = nil; + } + + if(![saveData writeToFile:kGameCenterManagerDataPath atomically:YES]) + NSLog(@"GameCenterManager:: failed to write file to %@", kGameCenterManagerDataPath); + } + + +#endif + if(self.useNSDefaults==YES) { + if(self.shouldCryptData) { + [defaults setSecureObject:playerDict forKey:[self localPlayerId]]; + } else { + [defaults setObject:playerDict forKey:[self localPlayerId]]; + } + [defaults synchronize]; + } } - - // Seeing an NSRangeException for an empty array when trying to remove the object - // Despite the check above in this scope that leaderboards count is > 0 - if (GCMLeaderboards.count > 0) { - [GCMLeaderboards removeObjectAtIndex:0]; + + if (self->GCMLeaderboards.count > 0 && self->leaderboardIndex >= 0) { + self->leaderboardIndex--; } - [self syncGameCenter]; } else { - dispatch_async(dispatch_get_main_queue(), ^{ - if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) - [[self delegate] gameCenterManager:self error:error]; - }); - } - }]; + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; +// [[NSUserDefaults standardUserDefaults] setBool:YES forKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]; +// NSLog(@"scoresSynced YES"); + } + } else { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]; + NSLog(@"scoresSynced YES"); [self syncGameCenter]; } - // Check if Achievements are synced - } else if (![[NSUserDefaults standardUserDefaults] boolForKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]) { + } else if (!achievementsSynced) { [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) { if (error == nil) { NSLog(@"Number of Achievements: %@", achievements); if (achievements.count > 0) { NSMutableDictionary *playerDict; - #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; - #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; - #endif +#if !TARGET_OS_TV + NSMutableDictionary *plistDict = nil; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + plistDict= [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + + if(error != nil) { + NSLog(@"GameCenterManager:: failed load plistDic with error: %@", error.description); + error = nil; + } + + + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } +#endif + NSUserDefaults *defaults = nil; + if(self.useNSDefaults==YES) { + defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } + if (playerDict == nil) { playerDict = [NSMutableDictionary dictionary]; @@ -456,20 +856,33 @@ - (void)syncGameCenter { for (GKAchievement *achievement in achievements) { [playerDict setObject:[NSNumber numberWithDouble:achievement.percentComplete] forKey:achievement.identifier]; } - #if !TARGET_OS_TV - [plistDict setObject:playerDict forKey:[self localPlayerId]]; - NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; - #else - [defaults setObject:playerDict forKey:[self localPlayerId]]; - [defaults synchronize]; - #endif +#if !TARGET_OS_TV + if(self.useNSDefaults == NO) { + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + if(error != nil) { + NSLog(@"GameCenterManager:: failed to saveData with error: %@", error.description); + error = nil; + } + + if(![saveData writeToFile:kGameCenterManagerDataPath atomically:YES]) + NSLog(@"GameCenterManager:: failed to write file to %@", kGameCenterManagerDataPath); + } +#endif + if(self.useNSDefaults == YES) { + if(self.shouldCryptData) { + [defaults setSecureObject:playerDict forKey:[self localPlayerId]]; + } else { + [defaults setObject:playerDict forKey:[self localPlayerId]]; + } + [defaults synchronize]; + } } [[NSUserDefaults standardUserDefaults] setBool:YES forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + NSLog(@"achievementsSynced YES"); [self syncGameCenter]; } else { dispatch_async(dispatch_get_main_queue(), ^{ @@ -482,6 +895,7 @@ - (void)syncGameCenter { [[NSUserDefaults standardUserDefaults] boolForKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]] == YES ) { // Game Center Synced dispatch_async(dispatch_get_main_queue(), ^{ + NSLog(@"gameCenterSynced YES"); if ([[self delegate] respondsToSelector:@selector(gameCenterManager:gameCenterSynced:)]) { [[self delegate] gameCenterManager:self gameCenterSynced:YES]; } @@ -494,12 +908,14 @@ - (void)syncGameCenter { if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) [[self delegate] gameCenterManager:self error:error]; }); + + // End the Background Process + [[UIApplication sharedApplication] endBackgroundTask:self->backgroundProcess]; + self->backgroundProcess = UIBackgroundTaskInvalid; } }); - // End the Background Process - [[UIApplication sharedApplication] endBackgroundTask:backgroundProcess]; - backgroundProcess = UIBackgroundTaskInvalid; + #else // Check if GameCenter is available if ([self checkGameCenterAvailability:NO] == YES) { @@ -522,47 +938,40 @@ - (void)syncGameCenter { } if (GCMLeaderboards.count > 0) { -#ifdef __MAC_10_10 - GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] initWithPlayers:[NSArray arrayWithObject:[GKLocalPlayer localPlayer]]]; - [leaderboardRequest setIdentifier:[(GKLeaderboard *)[GCMLeaderboards objectAtIndex:0] identifier]]; -#else - GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] initWithPlayerIDs:[NSArray arrayWithObject:[self localPlayerId]]]; - [leaderboardRequest setCategory:[(GKLeaderboard *)[GCMLeaderboards objectAtIndex:0] category]]; -#endif + GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] initWithPlayers:[NSArray arrayWithObject:[GKLocalPlayer localPlayer]]]; + [leaderboardRequest setIdentifier:[(GKLeaderboard *)[GCMLeaderboards objectAtIndex:0] identifier]]; + [leaderboardRequest loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) { if (error == nil) { if (scores.count > 0) { NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; if (playerDict == nil) { playerDict = [NSMutableDictionary dictionary]; } - float savedHighScoreValue = 0; -#ifdef __MAC_10_10 - NSNumber *savedHighScore = [playerDict objectForKey:leaderboardRequest.localPlayerScore.leaderboardIdentifier]; -#else - NSNumber *savedHighScore = [playerDict objectForKey:leaderboardRequest.localPlayerScore.category]; -#endif - + float savedHighScoreValue = 0; + NSNumber *savedHighScore = [playerDict objectForKey:leaderboardRequest.localPlayerScore.leaderboardIdentifier]; + + if (savedHighScore != nil) { savedHighScoreValue = [savedHighScore longLongValue]; } - -#ifdef __MAC_10_10 - [playerDict setObject:[NSNumber numberWithLongLong:MAX(leaderboardRequest.localPlayerScore.value, savedHighScoreValue)] forKey:leaderboardRequest.localPlayerScore.leaderboardIdentifier]; -#else - [playerDict setObject:[NSNumber numberWithLongLong:MAX(leaderboardRequest.localPlayerScore.value, savedHighScoreValue)] forKey:leaderboardRequest.localPlayerScore.category]; -#endif + + + [playerDict setObject:[NSNumber numberWithLongLong:MAX(leaderboardRequest.localPlayerScore.value, savedHighScoreValue)] forKey:leaderboardRequest.localPlayerScore.leaderboardIdentifier]; + [plistDict setObject:playerDict forKey:[self localPlayerId]]; NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; } @@ -592,9 +1001,11 @@ - (void)syncGameCenter { if (error == nil) { if (achievements.count > 0) { NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; if (playerDict == nil) { @@ -607,8 +1018,9 @@ - (void)syncGameCenter { [plistDict setObject:playerDict forKey:[self localPlayerId]]; NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; } @@ -623,7 +1035,7 @@ - (void)syncGameCenter { } }]; } else if( [[NSUserDefaults standardUserDefaults] boolForKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]] == YES && - [[NSUserDefaults standardUserDefaults] boolForKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]] == YES ) { + [[NSUserDefaults standardUserDefaults] boolForKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]] == YES ) { // Game Center Synced dispatch_async(dispatch_get_main_queue(), ^{ if ([[self delegate] respondsToSelector:@selector(gameCenterManager:gameCenterSynced:)]) { @@ -642,44 +1054,316 @@ - (void)syncGameCenter { #endif } -- (void)reportSavedScoresAndAchievements { + +- (void)reportSavedLeaderboardScoresAndAchievements { if ([self isInternetAvailable] == NO) return; + - GKScore *gkScore = nil; + GKLeaderboardScore *gkScore = nil; NSMutableArray *savedScores; - #if !TARGET_OS_TV + NSError * error; + + NSMutableDictionary *plistDict = [NSMutableDictionary dictionary]; + +#if !TARGET_OS_TV NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - savedScores = [plistDict objectForKey:@"SavedScores"]; - #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - savedScores = [[defaults objectForKey:@"SavedScores"] mutableCopy]; - #endif + + if(self.useNSDefaults == NO) { + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + savedScores = [plistDict objectForKey:@"SavedScores"]; + } +#endif + NSUserDefaults *defaults = nil; + if(self.useNSDefaults == YES) { + defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + NSData * savedData = [[defaults secureObjectForKey:@"SavedScoresRoot" valid:&valid] mutableCopy]; + if(savedData != nil) { + NSDictionary * dict = (NSDictionary*)savedData; + savedScores = [[dict objectForKey:@"SavedScores"] mutableCopy]; + } + } else { + NSData * savedData = [[defaults objectForKey:@"SavedScoresRoot"] mutableCopy]; + if(savedData != nil) { + NSDictionary * dict = (NSDictionary*)savedData; + savedScores = [[dict objectForKey:@"SavedScores"] mutableCopy]; + } + } + } if (savedScores != nil) { if (savedScores.count > 0) { - gkScore = [NSKeyedUnarchiver unarchiveObjectWithData:[savedScores objectAtIndex:0]]; + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:[savedScores objectAtIndex:0] error:&error]; + if(error != nil) { + NSLog(@"GameCenterManager::reportSavedLeaderboardScoresAndAchievements failed to load NSKeyedUnarchiver with error: %@", error.description); + error = nil; + } + [unarchiver setRequiresSecureCoding:NO]; + NSError* err = nil; + gkScore = [unarchiver decodeTopLevelObjectOfClass:[GKLeaderboardScore class] forKey:@"score" error:&err]; + if(err != nil) { + NSLog(@"GameCenterManager::reportSavedLeaderboardScoresAndAchievements decoding GKLeaderboard error: %@", err.description); + err = nil; + } + +#if !TARGET_OS_TV + if(self.useNSDefaults == NO) { + [plistDict setObject:savedScores forKey:@"SavedScores"]; + + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + if ([GKLocalPlayer localPlayer].authenticated) + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + else { + NSLog(@"GameCenterManager::reportSavedLeaderboardScoresAndAchievements not logged in - wont destroy the score"); + } + } +#endif + if(self.useNSDefaults == YES) { + + if(savedScores.count > 0) { + [savedScores removeObjectAtIndex:0]; + } + [plistDict setObject:savedScores forKey:@"SavedScores"]; + + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + + if(error != nil) { + NSLog(@"GameCenterManager::saveLeaderboardScoreToReportLater failed to archivedDataWithRootObject error: %@", error.description); + error = nil; + } + + if(self.shouldCryptData) { + [defaults setSecureObject:plistDict forKey:@"SavedScoresRoot"]; + } else { + [defaults setObject:plistDict forKey:@"SavedScoresRoot"]; + } + [defaults synchronize]; + } + } + } + + if (gkScore != nil && gkScore.value != 0) { + + GKLeaderboard * leaderboard = nil; + for(int i= 0; iGCMLeaderboards.count; i++) + { + GKLeaderboard * leaderboardRequest = (GKLeaderboard *)self->GCMLeaderboards[i]; + if(leaderboardRequest != nil && ([leaderboardRequest.baseLeaderboardID isEqualToString:gkScore.leaderboardID] == YES || leaderboardRequest.baseLeaderboardID == gkScore.leaderboardID)) { + leaderboard = leaderboardRequest; + break; + } + } + + if(leaderboard == nil) { + NSLog(@"saveAndReportScore:could not find leaderboard"); + return; + } + + if (@available(iOS 14.0, *)) { + [leaderboard submitScore:gkScore.value + context:gkScore.context + player:gkScore.player + completionHandler:^(NSError * _Nullable error) { + if (error == nil) { + NSLog(@"GameCenterManager - Offline Score to GameCenter Successful for: %@ score: %li context: %lu", gkScore.leaderboardID, gkScore.value, (unsigned long)gkScore.context); + [self reportSavedLeaderboardScoresAndAchievements]; + } else { + [self saveLeaderboardScoreToReportLater:gkScore]; + } + }]; + + } else { + GKScore *gkScoreprior = [[GKScore alloc] initWithLeaderboardIdentifier:gkScore.leaderboardID]; + + [gkScoreprior setValue:gkScore.value]; + + [GKScore reportScores:@[gkScoreprior] withCompletionHandler:^(NSError *error) { + NSDictionary *dict = nil; + + if (error == nil) { + dict = [NSDictionary dictionaryWithObjects:@[gkScoreprior] forKeys:@[@"score"]]; + } else { + dict = [NSDictionary dictionaryWithObjects:@[error.localizedDescription, gkScoreprior] forKeys:@[@"error", @"score"]]; + [self saveScoreToReportLater:gkScoreprior]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:reportedScore:withError:)]) + [[self delegate] gameCenterManager:self reportedScore:gkScoreprior withError:error]; + }); + }]; + } + } else { + if ([GKLocalPlayer localPlayer].authenticated) { + NSString *identifier = nil; + double percentComplete = 0; + + NSMutableDictionary *playerDict; + +#if !TARGET_OS_TV + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + playerDict = [plistDict objectForKey:[self localPlayerId]]; +#else + +#endif + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + + if (playerDict != nil) { + NSMutableDictionary *savedAchievements = [[playerDict objectForKey:@"SavedAchievements"] mutableCopy]; + if (savedAchievements != nil) { + if (savedAchievements.count > 0) { + identifier = [[savedAchievements allKeys] objectAtIndex:0]; + percentComplete = [[savedAchievements objectForKey:identifier] doubleValue]; + + [savedAchievements removeObjectForKey:identifier]; + [playerDict setObject:savedAchievements forKey:@"SavedAchievements"]; + +#if !TARGET_OS_TV + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; +#else + +#endif + if(self.shouldCryptData) { + [defaults setSecureObject:playerDict forKey:[self localPlayerId]]; + } else { + [defaults setObject:playerDict forKey:[self localPlayerId]]; + } + [defaults synchronize]; + } + } + } + if (identifier != nil) { + GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:identifier]; + achievement.percentComplete = percentComplete; + [GKAchievement reportAchievements:@[achievement] withCompletionHandler:^(NSError *error) { + if (error == nil) { + if (@available(iOS 14.0, *)) + [self reportSavedLeaderboardScoresAndAchievements]; + else + [self reportSavedScoresAndAchievements]; + } else { + [self saveAchievementToReportLater:achievement.identifier percentComplete:achievement.percentComplete]; + } + }]; + } + } + } +} + +- (void)reportSavedScoresAndAchievements { + if ([self isInternetAvailable] == NO) return; + + GKScore *gkScore = nil; + NSMutableArray *savedScores; + NSError * error; +#if !TARGET_OS_TV + NSMutableDictionary *plistDict = [NSMutableDictionary dictionary]; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + savedScores = [plistDict objectForKey:@"SavedScores"]; + } +#endif + NSUserDefaults *defaults = nil; + if(self.useNSDefaults == YES) { + defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + NSData * savedData = [[defaults secureObjectForKey:@"SavedScoresRoot" valid:&valid] mutableCopy]; + if(savedData != nil) { + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableArray.class fromData:savedData error:&error]; + if(error != nil) { + NSLog(@"GameCenterManager::reportSavedScoresAndAchievements failed to load NSData from NSKeyedUnarchiver with error: %@", error.description); + error = nil; + plistDict = [NSMutableDictionary dictionary]; + } + savedScores = [plistDict objectForKey:@"SavedScores"]; + } + + } else { + NSData * savedData = [[defaults objectForKey:@"SavedScoresRoot"] mutableCopy]; + if(savedData != nil) { + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:savedData error:&error]; + if(error != nil) { + NSLog(@"GameCenterManager::reportSavedScoresAndAchievements failed to load GKLeaderboardScore with error: %@", error.description); + error = nil; + plistDict = [NSMutableDictionary dictionary]; + } + savedScores = [plistDict objectForKey:@"SavedScores"]; + } + } + } + + if (savedScores != nil) { + if (savedScores.count > 0) { + gkScore = [NSKeyedUnarchiver unarchivedObjectOfClass:GKScore.class fromData:[savedScores objectAtIndex:0] error:&error]; [savedScores removeObjectAtIndex:0]; - #if !TARGET_OS_TV - [plistDict setObject:savedScores forKey:@"SavedScores"]; +#if !TARGET_OS_TV + if(self.useNSDefaults == NO) { + [plistDict setObject:savedScores forKey:@"SavedScores"]; + + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + if ([GKLocalPlayer localPlayer].authenticated) + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + else { + NSLog(@"GameCenterManager::reportSavedScoresAndAchievements not logged in - wont destroy the score"); + } + } +#else - NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; - #else - [defaults setObject:savedScores forKey:@"SavedScores"]; - [defaults synchronize]; - #endif +#endif + if(self.useNSDefaults == YES) { + [plistDict setObject:savedScores forKey:@"SavedScores"]; + + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + + if(error != nil) { + NSLog(@"GameCenterManager::reportSavedScoresAndAchievements failed to archivedDataWithRootObject error: %@", error.description); + error = nil; + } + + if(self.shouldCryptData) { + [defaults setSecureObject:plistDict forKey:@"SavedScoresRoot"]; + } else { + [defaults setObject:plistDict forKey:@"SavedScoresRoot"]; + } + [defaults synchronize]; + } } } if (gkScore != nil && gkScore.value != 0) { + [GKScore reportScores:@[gkScore] withCompletionHandler:^(NSError *error) { if (error == nil) { [self reportSavedScoresAndAchievements]; @@ -694,16 +1378,28 @@ - (void)reportSavedScoresAndAchievements { NSMutableDictionary *playerDict; - #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; - #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; - #endif +#if !TARGET_OS_TV + NSMutableDictionary *plistDict = nil; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } +#else + +#endif + NSUserDefaults *defaults = nil; + if(self.useNSDefaults == YES) { + [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } if (playerDict != nil) { NSMutableDictionary *savedAchievements = [[playerDict objectForKey:@"SavedAchievements"] mutableCopy]; @@ -715,16 +1411,22 @@ - (void)reportSavedScoresAndAchievements { [savedAchievements removeObjectForKey:identifier]; [playerDict setObject:savedAchievements forKey:@"SavedAchievements"]; - #if !TARGET_OS_TV - [plistDict setObject:playerDict forKey:[self localPlayerId]]; - NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; - #else - [defaults setObject:playerDict forKey:[self localPlayerId]]; - [defaults synchronize]; - #endif +#if !TARGET_OS_TV + if(self.useNSDefaults == NO) { + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } +#endif + if(self.useNSDefaults == YES) { + if(self.shouldCryptData) { + [defaults setSecureObject:playerDict forKey:[self localPlayerId]]; + } else { + [defaults setObject:playerDict forKey:[self localPlayerId]]; + } + [defaults synchronize]; + } } } } @@ -734,7 +1436,10 @@ - (void)reportSavedScoresAndAchievements { achievement.percentComplete = percentComplete; [GKAchievement reportAchievements:@[achievement] withCompletionHandler:^(NSError *error) { if (error == nil) { - [self reportSavedScoresAndAchievements]; + if (@available(iOS 14.0, *)) + [self reportSavedLeaderboardScoresAndAchievements]; + else + [self reportSavedScoresAndAchievements]; } else { [self saveAchievementToReportLater:achievement.identifier percentComplete:achievement.percentComplete]; } @@ -750,26 +1455,40 @@ - (void)reportSavedScoresAndAchievements { //------------------------------------------------------------------------------------------------------------// #pragma mark - Score and Achievement Reporting -- (void)saveAndReportScore:(long long)score leaderboard:(NSString *)identifier sortOrder:(GameCenterSortOrder)order { +- (void)saveAndReportScore:(long long)score leaderboard:(NSString *)identifier sortOrder:(GameCenterSortOrder)order { + [self saveAndReportScore:score context:0 leaderboard:identifier sortOrder:order]; +} + +- (void)saveAndReportScore:(long long)score context:(long long)context leaderboard:(NSString *)identifier sortOrder:(GameCenterSortOrder)order { NSMutableDictionary *playerDict; + NSError * error; #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; - #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + NSMutableDictionary *plistDict = nil; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } + #endif + NSUserDefaults *defaults = nil; + if(self.useNSDefaults == YES) { + defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } if (playerDict == nil) playerDict = [NSMutableDictionary dictionary]; NSNumber *savedHighScore = [playerDict objectForKey:identifier]; - //MARK:- issue arises here if (savedHighScore == nil) - savedHighScore = [NSNumber numberWithLongLong:-INFINITY]; + savedHighScore = [NSNumber numberWithLongLong:0]; long long savedHighScoreValue = [savedHighScore longLongValue]; @@ -784,80 +1503,140 @@ - (void)saveAndReportScore:(long long)score leaderboard:(NSString *)identifier s isScoreBetter = YES; break; } - - if (isScoreBetter) { - [playerDict setObject:[NSNumber numberWithLongLong:score] forKey:identifier]; - #if !TARGET_OS_TV - [plistDict setObject:playerDict forKey:[self localPlayerId]]; - NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; - #else - [defaults setObject:playerDict forKey:[self localPlayerId]]; - [defaults synchronize]; + + if (isScoreBetter) { + [playerDict setObject:[NSNumber numberWithLongLong:score] forKey:identifier]; + #if !TARGET_OS_TV + if(self.useNSDefaults == NO) { + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData; + NSError *error; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } #endif + if(self.useNSDefaults == YES) { + if(self.shouldCryptData) { + [defaults setSecureObject:playerDict forKey:[self localPlayerId]]; + } else { + [defaults setObject:playerDict forKey:[self localPlayerId]]; + } + [defaults synchronize]; + } } if ([self checkGameCenterAvailability:NO] == YES) { -#if TARGET_OS_IPHONE - GKScore *gkScore = [[GKScore alloc] initWithLeaderboardIdentifier:identifier]; -#else -#ifdef __MAC_10_10 - GKScore *gkScore = [[GKScore alloc] initWithLeaderboardIdentifier:identifier]; -#else - GKScore *gkScore = [[GKScore alloc] initWithCategory:identifier]; -#endif -#endif - [gkScore setValue:score]; - - [GKScore reportScores:@[gkScore] withCompletionHandler:^(NSError *error) { - NSDictionary *dict = nil; + + if (@available(iOS 14.0, *)) { - if (error == nil) { - dict = [NSDictionary dictionaryWithObjects:@[gkScore] forKeys:@[@"score"]]; - } else { - dict = [NSDictionary dictionaryWithObjects:@[error.localizedDescription, gkScore] forKeys:@[@"error", @"score"]]; - [self saveScoreToReportLater:gkScore]; + GKLeaderboard * leaderboard = nil; + for(int i= 0; iGCMLeaderboards.count; i++) + { + GKLeaderboard * leaderboardRequest = (GKLeaderboard *)self->GCMLeaderboards[i]; + if(leaderboardRequest != nil && ([leaderboardRequest.baseLeaderboardID isEqualToString:identifier] == YES || leaderboardRequest.baseLeaderboardID == identifier || leaderboardRequest.identifier == identifier || [leaderboardRequest.identifier isEqualToString:identifier] == YES)) { + leaderboard = leaderboardRequest; +// NSLog(@"saveAndReportScore: found searching:%@ for: %@", leaderboardRequest.baseLeaderboardID, identifier); + break; + } else { +// if(leaderboardRequest != nil) { +// NSLog(@"saveAndReportScore: searching:%@ for: %@", leaderboardRequest.baseLeaderboardID, identifier); +// } + } } - dispatch_async(dispatch_get_main_queue(), ^{ - if ([[self delegate] respondsToSelector:@selector(gameCenterManager:reportedScore:)]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [[self delegate] gameCenterManager:self reportedScore:dict]; -#pragma clang diagnostic pop - } else if ([[self delegate] respondsToSelector:@selector(gameCenterManager:reportedScore:withError:)]) - [[self delegate] gameCenterManager:self reportedScore:gkScore withError:error]; - }); - }]; + + if(leaderboard == nil) { + NSLog(@"saveAndReportScore:could not find leaderboard"); + return; + } + + GKLeaderboardScore * gkScore = [GKLeaderboardScore alloc]; + gkScore.leaderboardID = identifier; + gkScore.player = [GKLocalPlayer localPlayer]; + gkScore.value = score; + gkScore.context = context; + + [leaderboard submitScore:score + context:context + player:[GKLocalPlayer localPlayer] completionHandler:^(NSError * _Nullable error) { + NSDictionary *dict = nil; + + if (error == nil) { + dict = [NSDictionary dictionaryWithObjects:@[gkScore] forKeys:@[@"leaderboardscore"]]; + } else { + dict = [NSDictionary dictionaryWithObjects:@[error.localizedDescription, gkScore] forKeys:@[@"error", @"leaderboardscore"]]; + [self saveLeaderboardScoreToReportLater:gkScore]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:reportedLeaderboardScore:withError:)]) + [[self delegate] gameCenterManager:self reportedLeaderboardScore:gkScore withError:error]; + }); + }]; + + + } else { + GKScore *gkScore = [[GKScore alloc] initWithLeaderboardIdentifier:identifier]; + + [gkScore setValue:score]; + + [GKScore reportScores:@[gkScore] withCompletionHandler:^(NSError *error) { + NSDictionary *dict = nil; + + if (error == nil) { + dict = [NSDictionary dictionaryWithObjects:@[gkScore] forKeys:@[@"score"]]; + } else { + dict = [NSDictionary dictionaryWithObjects:@[error.localizedDescription, gkScore] forKeys:@[@"error", @"score"]]; + [self saveScoreToReportLater:gkScore]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:reportedScore:withError:)]) + [[self delegate] gameCenterManager:self reportedScore:gkScore withError:error]; + }); + }]; + } + } else { -#if TARGET_OS_IPHONE - GKScore *gkScore = [[GKScore alloc] initWithLeaderboardIdentifier:identifier]; -#else -#ifdef __MAC_10_10 - GKScore *gkScore = [[GKScore alloc] initWithLeaderboardIdentifier:identifier]; -#else - GKScore *gkScore = [[GKScore alloc] initWithCategory:identifier]; -#endif -#endif - [gkScore setValue:score]; - [self saveScoreToReportLater:gkScore]; + if (@available(iOS 14.0, *)) { + GKLeaderboardScore * gkScore = [GKLeaderboardScore alloc]; + gkScore.leaderboardID = identifier; + gkScore.player = [GKLocalPlayer localPlayer]; + gkScore.value = score; + gkScore.context = context; + [self saveLeaderboardScoreToReportLater:gkScore]; + } + else { + GKScore *gkScore = [[GKScore alloc] initWithLeaderboardIdentifier:identifier]; + + [gkScore setValue:score]; + [self saveScoreToReportLater:gkScore]; + } } } - (void)saveAndReportAchievement:(NSString *)identifier percentComplete:(double)percentComplete shouldDisplayNotification:(BOOL)displayNotification { NSMutableDictionary *playerDict; + NSError * error; #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; - #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + + NSMutableDictionary *plistDict = nil; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } #endif + NSUserDefaults *defaults = nil; + if(self.useNSDefaults == YES) { + defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } } if (playerDict == nil) playerDict = [NSMutableDictionary dictionary]; @@ -870,15 +1649,24 @@ - (void)saveAndReportAchievement:(NSString *)identifier percentComplete:(double) if (percentComplete > savedPercentCompleteValue) { [playerDict setObject:[NSNumber numberWithDouble:percentComplete] forKey:identifier]; #if !TARGET_OS_TV - [plistDict setObject:playerDict forKey:[self localPlayerId]]; - NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + if(self.useNSDefaults == NO) { + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } #else - [defaults setObject:playerDict forKey:[self localPlayerId]]; - [defaults synchronize]; + #endif + if(self.useNSDefaults == YES) { + if(self.shouldCryptData) { + [defaults setSecureObject:playerDict forKey:[self localPlayerId]]; + } else { + [defaults setObject:playerDict forKey:[self localPlayerId]]; + } + [defaults synchronize]; + } } if ([self checkGameCenterAvailability:NO] == YES) { @@ -901,13 +1689,9 @@ - (void)saveAndReportAchievement:(NSString *)identifier percentComplete:(double) } dispatch_async(dispatch_get_main_queue(), ^{ - if ([[self delegate] respondsToSelector:@selector(gameCenterManager:reportedAchievement:)]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [[self delegate] gameCenterManager:self reportedAchievement:dict]; -#pragma clang diagnostic pop - } else if ([[self delegate] respondsToSelector:@selector(gameCenterManager:reportedAchievement:withError:)]) - [[self delegate] gameCenterManager:self reportedAchievement:achievement withError:error]; + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:reportedAchievement:withError:)]) { + [[self delegate] gameCenterManager:self reportedAchievement:achievement withError:error]; + } }); }]; @@ -916,65 +1700,176 @@ - (void)saveAndReportAchievement:(NSString *)identifier percentComplete:(double) } } +- (void)saveLeaderboardScoreToReportLater:(GKLeaderboardScore *)score API_AVAILABLE(ios(14.0)){ + if(score.value <= 0) { + return; + } + NSError * error = nil; + NSMutableDictionary *plistDict = [NSMutableDictionary dictionary]; + NSMutableArray *savedScores = nil; + + NSData *scoreData; + + NSKeyedArchiver * archiver; + if (@available(iOS 11.0, *)) { + archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:false]; + } else { + archiver = [[NSKeyedArchiver alloc] init]; + } + [archiver encodeObject:score forKey:@"score"]; + scoreData = [archiver encodedData]; + if(error != nil) { + NSLog(@"GameCenterManager::saveLeaderboardScoreToReportLater failed to load NSData from NSKeyedUnarchiver with error: %@", error.description); + error = nil; + } + + #if !TARGET_OS_TV + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + savedScores = [plistDict objectForKey:@"SavedScores"]; + } + #endif + NSUserDefaults *defaults = nil; + if(self.useNSDefaults == YES) { + defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + NSData * savedData = [[defaults secureObjectForKey:@"SavedScoresRoot" valid:&valid] mutableCopy]; + if(savedData != nil) { + NSDictionary * dict = (NSDictionary*)savedData; + savedScores = [[dict objectForKey:@"SavedScores"] mutableCopy]; + } + } else { + NSData * savedData = [[defaults objectForKey:@"SavedScoresRoot"] mutableCopy]; + if(savedData != nil) { + NSDictionary * dict = (NSDictionary*)savedData; + savedScores = [[dict objectForKey:@"SavedScores"] mutableCopy]; + } + } + if (savedScores != nil) { + [savedScores addObject:scoreData]; + } else { + savedScores = [NSMutableArray arrayWithObject:scoreData]; + } + [plistDict setObject:savedScores forKey:@"SavedScores"]; + + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + + if(error != nil) { + NSLog(@"GameCenterManager::saveLeaderboardScoreToReportLater failed to archivedDataWithRootObject error: %@", error.description); + error = nil; + } + + if(self.shouldCryptData) { + [defaults setSecureObject:plistDict forKey:@"SavedScoresRoot"]; + } else { + [defaults setObject:plistDict forKey:@"SavedScoresRoot"]; + } + [defaults synchronize]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:didSaveLeaderboardScore:)]) { + [[self delegate] gameCenterManager:self didSaveLeaderboardScore:score]; + + } + }); +} + - (void)saveScoreToReportLater:(GKScore *)score { if(score.value == 0) { return; } - NSData *scoreData = [NSKeyedArchiver archivedDataWithRootObject:score]; + NSError * error; + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:score requiringSecureCoding:self.shouldCryptNSData error:&error]; + NSMutableArray *savedScores; #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - savedScores = [plistDict objectForKey:@"SavedScores"]; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + + NSMutableDictionary *plistDict; + + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + + savedScores = [plistDict objectForKey:@"SavedScores"]; + } #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - savedScores = [[defaults objectForKey:@"SavedScores"] mutableCopy]; + #endif - - if (savedScores != nil) { - [savedScores addObject:scoreData]; - } else { - savedScores = [NSMutableArray arrayWithObject:scoreData]; + if(self.useNSDefaults == YES) { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + savedScores = [[defaults secureObjectForKey:@"SavedScores" valid:&valid] mutableCopy]; + } else { + savedScores = [[defaults objectForKey:@"SavedScores"] mutableCopy]; + } + if (savedScores != nil) { + [savedScores addObject:saveData]; + } else { + savedScores = [NSMutableArray arrayWithObject:saveData]; + } + if(self.shouldCryptData) { + [defaults setSecureObject:savedScores forKey:@"SavedScores"]; + } else { + [defaults setObject:savedScores forKey:@"SavedScores"]; + } + [defaults synchronize]; } + + #if !TARGET_OS_TV - [plistDict setObject:savedScores forKey:@"SavedScores"]; - NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; +// [plistDict setObject:savedScores forKey:@"SavedScores"]; +// NSData *saveData; +// saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; +// #else - [defaults setObject:savedScores forKey:@"SavedScores"]; - [defaults synchronize]; +// #endif dispatch_async(dispatch_get_main_queue(), ^{ if ([[self delegate] respondsToSelector:@selector(gameCenterManager:didSaveScore:)]) { [[self delegate] gameCenterManager:self didSaveScore:score]; - } else if ([[self delegate] respondsToSelector:@selector(gameCenterManager:savedScore:)]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [[self delegate] gameCenterManager:self savedScore:score]; -#pragma clang diagnostic pop + } }); } - (void)saveAchievementToReportLater:(NSString *)identifier percentComplete:(double)percentComplete { NSMutableDictionary *playerDict; + NSError * error; #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; - #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + NSMutableDictionary *plistDict = nil; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } + #endif + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + if(self.useNSDefaults == YES) { + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } if (playerDict != nil) { NSMutableDictionary *savedAchievements = [[playerDict objectForKey:@"SavedAchievements"] mutableCopy]; @@ -1001,15 +1896,23 @@ - (void)saveAchievementToReportLater:(NSString *)identifier percentComplete:(dou } #if !TARGET_OS_TV - [plistDict setObject:playerDict forKey:[self localPlayerId]]; - NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + if(self.useNSDefaults == NO) { + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData; + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } #else - [defaults setObject:playerDict forKey:[self localPlayerId]]; - [defaults synchronize]; + #endif + if(self.useNSDefaults == YES) { + if(self.shouldCryptData) { + [defaults setSecureObject:playerDict forKey:[self localPlayerId]]; + } else { + [defaults setObject:playerDict forKey:[self localPlayerId]]; + } + [defaults synchronize]; + } GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:identifier]; NSNumber *percentNumber = [NSNumber numberWithDouble:percentComplete]; @@ -1042,18 +1945,34 @@ - (void)saveAchievementToReportLater:(NSString *)identifier percentComplete:(dou - (long long)highScoreForLeaderboard:(NSString *)identifier { + NSError * error; NSMutableDictionary *playerDict; #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + + if(error != nil) { + NSLog(@"GameCenterManager:: failed to load plistDict with error: %@", error.description); + error = nil; + } + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; #endif + if(self.useNSDefaults == YES) { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } + if (playerDict != nil) { NSNumber *savedHighScore = [playerDict objectForKey:identifier]; if (savedHighScore != nil) { @@ -1062,22 +1981,40 @@ - (long long)highScoreForLeaderboard:(NSString *)identifier { return 0; } } else { + NSLog(@"GameCenterManager::highScoreForLeaderboard Error:playerDict is nil"); return 0; } } - (NSDictionary *)highScoreForLeaderboards:(NSArray *)identifiers { + NSError * error; NSMutableDictionary *playerDict; #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + if(error != nil) { + NSLog(@"GameCenterManager:: failed to load plistDict with error: %@", error.description); + error = nil; + } + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + #endif + if(self.useNSDefaults == YES) { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } NSMutableDictionary *highScores = [[NSMutableDictionary alloc] initWithCapacity:identifiers.count]; @@ -1102,39 +2039,76 @@ - (NSDictionary *)highScoreForLeaderboards:(NSArray *)identifiers { - (double)progressForAchievement:(NSString *)identifier { NSMutableDictionary *playerDict; + NSError * error; #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + if(error != nil) { + NSLog(@"GameCenterManager:: failed to load plistDict with error: %@", error.description); + error = nil; + } + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + #endif + if(self.useNSDefaults == YES) { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } if (playerDict != nil) { NSNumber *savedPercentComplete = [playerDict objectForKey:identifier]; - + double returnValue =[savedPercentComplete doubleValue]; if (savedPercentComplete != nil) { - return [savedPercentComplete doubleValue]; + return returnValue; } + } else { + NSLog(@"GameCenterManager::progressForAchievement Error:playerDict is nil"); + return 0; } return 0; } - (NSDictionary *)progressForAchievements:(NSArray *)identifiers { NSMutableDictionary *playerDict; + NSError * error; #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + if(error != nil) { + NSLog(@"GameCenterManager:: failed to load plistDict with error: %@", error.description); + error = nil; + } + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + #endif + if(self.useNSDefaults == YES) { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } NSMutableDictionary *percent = [[NSMutableDictionary alloc] initWithCapacity:identifiers.count]; @@ -1191,10 +2165,14 @@ - (void)getChallengesWithCompletion:(void (^)(NSArray *challenges, NSError *erro #if TARGET_OS_IPHONE - (void)presentAchievementsOnViewController:(UIViewController *)viewController { - GKGameCenterViewController *achievementsViewController = [[GKGameCenterViewController alloc] init]; - #if TARGET_OS_IOS || (TARGET_OS_IPHONE && !TARGET_OS_TV) - achievementsViewController.viewState = GKGameCenterViewControllerStateAchievements; - #endif + GKGameCenterViewController *achievementsViewController = nil; + if (@available(iOS 14.0, *)) { + achievementsViewController = [[GKGameCenterViewController alloc] initWithState:GKGameCenterViewControllerStateAchievements]; + } else { + // Fallback on earlier versions + achievementsViewController = [[GKGameCenterViewController alloc] init]; + achievementsViewController.viewState = GKGameCenterViewControllerStateAchievements; + } achievementsViewController.gameCenterDelegate = self; [viewController presentViewController:achievementsViewController animated:YES completion:^{ dispatch_async(dispatch_get_main_queue(), ^{ @@ -1204,25 +2182,26 @@ - (void)presentAchievementsOnViewController:(UIViewController *)viewController { }]; } -// left here for backwards compatibility -- (void)presentLeaderboardsOnViewController:(UIViewController *)viewController { - NSLog(@"WARNING: Calling a deprecated GameCenterManager method that may become obsolete in future versions. Use presentLeaderboardsOnViewController: withLeaderboard: instead. %s", __PRETTY_FUNCTION__); - [self presentLeaderboardsOnViewController:viewController withLeaderboard:nil]; -} - (void)presentLeaderboardsOnViewController:(UIViewController *)viewController withLeaderboard:(NSString *)leaderboard { - GKGameCenterViewController *leaderboardViewController = [[GKGameCenterViewController alloc] init]; - #if TARGET_OS_IOS || (TARGET_OS_IPHONE && !TARGET_OS_TV) - leaderboardViewController.viewState = GKGameCenterViewControllerStateLeaderboards; - /* - Passing nil to leaderboardViewController.leaderboardIdentifier works fine, - but to make sure futur updates will not brake, we'll check it first - */ - if (leaderboard != nil) { - leaderboardViewController.leaderboardIdentifier = leaderboard; - } - #elif TARGET_OS_TV - #warning For tvOS you must set leaderboard ID's in the Assets catalogue - Click on this warning for more info. + + GKGameCenterViewController *leaderboardViewController = nil; + if (@available(iOS 14.0, *)) { + leaderboardViewController = [[GKGameCenterViewController alloc] initWithLeaderboardID:leaderboard playerScope:GKLeaderboardPlayerScopeGlobal timeScope:GKLeaderboardTimeScopeWeek]; + + }else { + // Fallback on earlier versions + leaderboardViewController = [[GKGameCenterViewController alloc] init]; + leaderboardViewController.viewState = GKGameCenterViewControllerStateLeaderboards; + + if (leaderboard != nil) { + leaderboardViewController.leaderboardIdentifier = leaderboard; + } + } + + +#if TARGET_OS_TV +#warning For tvOS you must set leaderboard ID's in the Assets catalogue - Click on this warning for more info. /** To get the Leaderboards to show up: 1. Achievements and Leaderboards are merged into a single GameCenter view, with the Leaderboards shown above the achievements. @@ -1231,14 +2210,14 @@ - (void)presentLeaderboardsOnViewController:(UIViewController *)viewController w 4. You must set the "Identifier" for this Leaderboard asset to exactly what your identifier is for each of your leaderboards. Example: grp.GameCenterManager.PlayerScores 5. You must have the image sizes 659 × 371 for the Leaderboard Images.*/ - #endif - leaderboardViewController.gameCenterDelegate = self; - [viewController presentViewController:leaderboardViewController animated:YES completion:^{ - dispatch_async(dispatch_get_main_queue(), ^{ - if ([[self delegate] respondsToSelector:@selector(gameCenterManager:gameCenterViewControllerPresented:)]) - [[self delegate] gameCenterManager:self gameCenterViewControllerPresented:YES]; - }); - }]; +#endif + leaderboardViewController.gameCenterDelegate = self; + [viewController presentViewController:leaderboardViewController animated:YES completion:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:gameCenterViewControllerPresented:)]) + [[self delegate] gameCenterManager:self gameCenterViewControllerPresented:YES]; + }); + }]; } - (void)presentChallengesOnViewController:(UIViewController *)viewController { @@ -1266,7 +2245,7 @@ - (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCent }]; #else [gameCenterViewController dismissViewController:gameCenterViewController]; - dispatch_async(dispatch_get_main_queue(), ^{ + dispatch_async(dispatch_get_main_queue(), ^{ if ([[self delegate] respondsToSelector:@selector(gameCenterManager:gameCenterViewControllerDidFinish:)]) [[self delegate] gameCenterManager:self gameCenterViewControllerDidFinish:YES]; }); @@ -1285,15 +2264,33 @@ - (void)resetAchievementsWithCompletion:(void (^)(NSError *))handler { NSMutableDictionary *playerDict; #if !TARGET_OS_TV - NSData *gameCenterManagerData; - if (self.shouldCryptData == YES) gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:self.cryptKeyData]; - else gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; - NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; - playerDict = [plistDict objectForKey:[self localPlayerId]]; + NSMutableDictionary *plistDict = nil; + if(self.useNSDefaults == NO) { + NSData *gameCenterManagerData; + if (self.shouldCryptData == YES) + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + else + gameCenterManagerData = [NSData dataWithContentsOfFile:kGameCenterManagerDataPath]; + plistDict = [NSKeyedUnarchiver unarchivedObjectOfClass:NSMutableDictionary.class fromData:gameCenterManagerData error:&error]; + if(error != nil) { + NSLog(@"GameCenterManager:: failed to load plistDict with error: %@", error.description); + error = nil; + } + playerDict = [plistDict objectForKey:[self localPlayerId]]; + } #else - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + #endif + NSUserDefaults *defaults =nil; + if(self.useNSDefaults == YES) { + defaults = [NSUserDefaults standardUserDefaults]; + BOOL valid = NO; + if(self.shouldCryptData) { + if(playerDict == nil) playerDict = [[defaults secureObjectForKey:[self localPlayerId] valid:&valid] mutableCopy]; + } else { + if(playerDict == nil) playerDict = [[defaults objectForKey:[self localPlayerId]] mutableCopy]; + } + } if (playerDict == nil) { playerDict = [NSMutableDictionary dictionary]; @@ -1304,15 +2301,26 @@ - (void)resetAchievementsWithCompletion:(void (^)(NSError *))handler { } #if !TARGET_OS_TV - [plistDict setObject:playerDict forKey:[self localPlayerId]]; - NSData *saveData; - if (self.shouldCryptData == YES) saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:self.cryptKeyData]; - else saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict]; - [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + if(self.useNSDefaults == NO) { + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData; + + saveData = [NSKeyedArchiver archivedDataWithRootObject:plistDict requiringSecureCoding:self.shouldCryptNSData error:&error]; + + + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } #else - [defaults setObject:playerDict forKey:[self localPlayerId]]; - [defaults synchronize]; + #endif + if(self.useNSDefaults == YES) { + if(self.shouldCryptData) { + [defaults setSecureObject:playerDict forKey:[self localPlayerId]]; + } else { + [defaults setObject:playerDict forKey:[self localPlayerId]]; + } + [defaults synchronize]; + } [GKAchievement resetAchievementsWithCompletionHandler:^(NSError *error) { if (error == nil) { @@ -1347,10 +2355,15 @@ - (void)resetAchievementsWithCompletion:(void (^)(NSError *))handler { - (NSString *)localPlayerId { if ([self isGameCenterAvailable]) { if ([GKLocalPlayer localPlayer].authenticated) { - return [GKLocalPlayer localPlayer].playerID; + if (@available(iOS 12.4, *)) { + return [GKLocalPlayer localPlayer].teamPlayerID; + } else { + // Fallback on earlier versions + return [GKLocalPlayer localPlayer].playerID; + } } } - return @"unknownPlayer"; + return self.lastPlayerID; } - (NSString *)localPlayerDisplayName { @@ -1362,7 +2375,7 @@ - (NSString *)localPlayerDisplayName { } } - return @"unknownPlayer"; + return self.lastPlayerID; } - (GKLocalPlayer *)localPlayerData { diff --git a/GC Manager/NSDataAES256.h b/GC Manager/NSDataAES256.h old mode 100755 new mode 100644 diff --git a/GC Manager/NSDataAES256.m b/GC Manager/NSDataAES256.m old mode 100755 new mode 100644 diff --git a/GC Manager/NSUserDefaults+MPSecureUserDefaults.h b/GC Manager/NSUserDefaults+MPSecureUserDefaults.h new file mode 100644 index 0000000..23196fa --- /dev/null +++ b/GC Manager/NSUserDefaults+MPSecureUserDefaults.h @@ -0,0 +1,67 @@ +// +// NSUserDefaults+MPSecureUserDefaults.h +// Secure-NSUserDefaults +// +// Copyright (c) 2011 Matthias Plappert +// Modified by Daniel Rosser for Super Hexagon on 24/8/2022 + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +// to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import +#import + + +@interface NSUserDefaults (MPSecureUserDefaults) + +/** + * Sets the secret. Make sure that your secret is stored in a save place, it is recommanded to write it + * directly into your code. Required property. + */ ++ (void)setSecret:(NSString *)secret; + +/** + * Sets the device identifier. You can use this to link user defaults to a specific machine. + * This is particularly useful if users are likely to share plist files, e.g. if you use user defaults + * to store product license information. Optional property. + */ ++ (void)setDeviceIdentifier:(NSString *)deviceIdentifier; + +/** + * Read data from user defaults. If key doesn't exist, valid is YES and the function mimics + * the return behavior of the respective non-secure method. Please note that the methods below + * will always return the result, even if it is *NOT* secure. This is a change from previous versions + * of Secure-NSUserDefaults. It is therefore necessary to check to figure out an appropriate consequence + * for invalid defaults. + */ +- (NSArray *)secureArrayForKey:(NSString *)key valid:(BOOL *)valid; +- (BOOL)secureBoolForKey:(NSString *)key valid:(BOOL *)valid; +- (NSData *)secureDataForKey:(NSString *)key valid:(BOOL *)valid; +- (NSDictionary *)secureDictionaryForKey:(NSString *)key valid:(BOOL *)valid; +- (float)secureFloatForKey:(NSString *)key valid:(BOOL *)valid; +- (NSInteger)secureIntegerForKey:(NSString *)key valid:(BOOL *)valid; +- (id)secureObjectForKey:(NSString *)key valid:(BOOL *)valid; +- (NSArray *)secureStringArrayForKey:(NSString *)key valid:(BOOL *)valid; +- (NSString *)secureStringForKey:(NSString *)key valid:(BOOL *)valid; +- (double)secureDoubleForKey:(NSString *)key valid:(BOOL *)valid; + +/** + * Write data to user defaults. Only property list objects (NSData, NSString, NSNumber, NSDate, NSArray, NSDictionary) + * are supported. Passing nil as either the value or key mimics the behavior of the non-secure method. + */ +- (void)setSecureBool:(BOOL)value forKey:(NSString *)key; +- (void)setSecureFloat:(float)value forKey:(NSString *)key; +- (void)setSecureInteger:(NSInteger)value forKey:(NSString *)key; +- (void)setSecureObject:(id)value forKey:(NSString *)key; +- (void)setSecureDouble:(double)value forKey:(NSString *)key; + +@end diff --git a/GC Manager/NSUserDefaults+MPSecureUserDefaults.m b/GC Manager/NSUserDefaults+MPSecureUserDefaults.m new file mode 100644 index 0000000..e562ac2 --- /dev/null +++ b/GC Manager/NSUserDefaults+MPSecureUserDefaults.m @@ -0,0 +1,338 @@ +// +// NSUserDefaults+MPSecureUserDefaults.m +// Secure-NSUserDefaults +// +// Copyright (c) 2011 Matthias Plappert +// Modified by Daniel Rosser for Super Hexagon on 24/8/2022 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +// to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "NSUserDefaults+MPSecureUserDefaults.h" + + +static NSString *const MPSecureUserDefaultsValueKey = @"MPSecureUserDefaultsValue"; +static NSString *const MPSecureUserDefaultsHashKey = @"MPSecureUserDefaultsHash"; + + +@interface NSUserDefaults (MPSecureUserDefaultsPrivate) + +- (BOOL)_isValidPropertyListObject:(id)object; +- (id)_objectForKey:(NSString *)key valid:(BOOL *)valid; +- (NSString *)_hashObject:(id)object; +- (NSString *)_hashData:(NSData *)data; + +@end + + +@implementation NSUserDefaults (MPSecureUserDefaults) + +#pragma mark - Static + +static NSData *_secretData = nil; +static NSData *_deviceIdentifierData = nil; + ++ (void)setSecret:(NSString *)secret +{ + if (_secretData == nil) { + _secretData = [secret dataUsingEncoding:NSUTF8StringEncoding]; + } else { + NSAssert(NO, @"The secret has already been set."); + } +} + ++ (void)setDeviceIdentifier:(NSString *)deviceIdentifier +{ + if (_deviceIdentifierData == nil) { + _deviceIdentifierData = [deviceIdentifier dataUsingEncoding:NSUTF8StringEncoding]; + } else { + NSAssert(NO, @"The device identifier has already been set."); + } +} + +#pragma mark - Read Accessors + +- (NSArray *)secureArrayForKey:(NSString *)key valid:(BOOL *)valid +{ + id object = [self secureObjectForKey:key valid:valid]; + if ([object isKindOfClass:[NSArray class]]) { + return object; + } else { + return nil; + } +} + +- (BOOL)secureBoolForKey:(NSString *)key valid:(BOOL *)valid +{ + id object = [self secureObjectForKey:key valid:valid]; + if ([object respondsToSelector:@selector(boolValue)]) { + return [object boolValue]; + } else { + return NO; + } +} + +- (NSData *)secureDataForKey:(NSString *)key valid:(BOOL *)valid +{ + id object = [self secureObjectForKey:key valid:valid]; + if ([object isKindOfClass:[NSData class]]) { + return object; + } else { + return nil; + } +} + +- (NSDictionary *)secureDictionaryForKey:(NSString *)key valid:(BOOL *)valid +{ + id object = [self secureObjectForKey:key valid:valid]; + if ([object isKindOfClass:[NSDictionary class]]) { + return object; + } else { + return nil; + } +} + +- (float)secureFloatForKey:(NSString *)key valid:(BOOL *)valid +{ + id object = [self secureObjectForKey:key valid:valid]; + if ([object respondsToSelector:@selector(floatValue)]) { + return [object floatValue]; + } else { + return 0.0f; + } +} + +- (NSInteger)secureIntegerForKey:(NSString *)key valid:(BOOL *)valid +{ + id object = [self secureObjectForKey:key valid:valid]; + if ([object respondsToSelector:@selector(intValue)]) { + return [object intValue]; + } else { + return 0; + } +} + +- (id)secureObjectForKey:(NSString *)key valid:(BOOL *)valid +{ + id object = [self _objectForKey:key valid:valid]; + return object; +} + +- (NSArray *)secureStringArrayForKey:(NSString *)key valid:(BOOL *)valid +{ + id object = [self secureObjectForKey:key valid:valid]; + if ([object isKindOfClass:[NSArray class]]) { + for (id child in object) { + if (![child isKindOfClass:[NSString class]]) { + return nil; + } + } + return object; + } else { + return nil; + } +} + +- (NSString *)secureStringForKey:(NSString *)key valid:(BOOL *)valid +{ + id object = [self secureObjectForKey:key valid:valid]; + if ([object isKindOfClass:[NSString class]]) { + return object; + } else if ([object respondsToSelector:@selector(stringValue)]) { + return [object stringValue]; + } else { + return nil; + } +} + +- (double)secureDoubleForKey:(NSString *)key valid:(BOOL *)valid +{ + id object = [self secureObjectForKey:key valid:valid]; + if ([object respondsToSelector:@selector(doubleValue)]) { + return [object doubleValue]; + } else { + return 0.0f; + } +} + +#pragma mark - Write Accessors + +- (void)setSecureBool:(BOOL)value forKey:(NSString *)key +{ + [self setSecureObject:[NSNumber numberWithBool:value] forKey:key]; +} + +- (void)setSecureFloat:(float)value forKey:(NSString *)key +{ + [self setSecureObject:[NSNumber numberWithFloat:value] forKey:key]; +} + +- (void)setSecureInteger:(NSInteger)value forKey:(NSString *)key +{ + [self setSecureObject:[NSNumber numberWithInteger:value] forKey:key]; +} + +- (void)setSecureObject:(id)value forKey:(NSString *)key +{ + if (value == nil || key == nil) { + // Use non-secure method + [self setObject:value forKey:key]; + + } else if ([self _isValidPropertyListObject:value]) { + NSString *hash = [self _hashObject:value]; + if (hash != nil) { + NSDictionary *dict = @{MPSecureUserDefaultsValueKey: value, + MPSecureUserDefaultsHashKey: hash}; + [self setObject:dict forKey:key]; + } + } +} + +- (void)setSecureDouble:(double)value forKey:(NSString *)key +{ + [self setSecureObject:[NSNumber numberWithDouble:value] forKey:key]; +} + +#pragma mark - Private Methods + +- (BOOL)_isValidPropertyListObject:(id)object +{ + if ([object isKindOfClass:[NSData class]] || [object isKindOfClass:[NSString class]] || + [object isKindOfClass:[NSNumber class]] || [object isKindOfClass:[NSDate class]]) { + return YES; + + } else if ([object isKindOfClass:[NSDictionary class]]) { + for (NSString *key in object) { + if (![self _isValidPropertyListObject:key]) { + // Abort + return NO; + } else { + id value = [object objectForKey:key]; + if (![self _isValidPropertyListObject:value]) { + // Abort + return NO; + } + } + } + return YES; + + } else if ([object isKindOfClass:[NSArray class]]) { + for (id value in object) { + if (![self _isValidPropertyListObject:value]) { + // Abort + return NO; + } + } + return YES; + + } else { + static NSString *format = @"*** -[NSUserDefaults setSecureObject:forKey:]: Attempt to insert non-property value '%@' of class '%@'."; + NSLog(format, object, NSStringFromClass([object class])); + return NO; + } +} + +- (id)_objectForKey:(NSString *)key valid:(BOOL *)valid +{ + NSDictionary *dict = [self dictionaryForKey:key]; + if (dict == nil) { + // Doesn't exist, valid but nil + if (valid) *valid = YES; + return nil; + } + + if (![dict isKindOfClass:[NSDictionary class]]) { + // Not a dictionary -> invalid + if (valid) *valid = NO; + return nil; + } + + id value = [dict objectForKey:MPSecureUserDefaultsValueKey]; + if (value == nil) { + // Value nil -> invalid + if (valid) *valid = NO; + return nil; + } + + NSString *hash = [dict objectForKey:MPSecureUserDefaultsHashKey]; + NSString *validationHash = [self _hashObject:value]; + if (hash == nil || validationHash == nil || ![hash isEqualToString:validationHash]) { + // Invalid hash -> invalid, but still return the value. + if (valid) *valid = NO; + return value; + } + + // Object is valid + if (valid) *valid = YES; + return value; +} + +- (NSString *)_hashObject:(id)object +{ + if (_secretData == nil) { + // Use if statement in case asserts are disabled + NSAssert(NO, @"Provide a secret before using any secure writing or reading methods!"); + return nil; + } + + // Copy object to make sure it is immutable (thanks Stephen) + object = [object copy]; + + // Archive & hash + //NSMutableData *archivedData = [[NSKeyedArchiver archivedDataWithRootObject:object] mutableCopy]; // 'archivedDataWithRootObject:' is deprecated: first deprecated in iOS 12.0 - Use +archivedDataWithRootObject:requiringSecureCoding:error: instead + + NSError * error = nil; + NSMutableData *archivedData = [[NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:YES error:&error] mutableCopy]; + + if(error != nil) { + NSLog(@"error: %@", error.description); + error = nil; + } + + [archivedData appendData:_secretData]; + if (_deviceIdentifierData != nil) { + [archivedData appendData:_deviceIdentifierData]; + } + NSString *hash = [self _hashData:archivedData]; + + return hash; +} + +- (NSString *)_hashData:(NSData *)data +{ +// const char *cStr = [data bytes]; +// unsigned char digest[CC_MD5_DIGEST_LENGTH]; + //CC_MD5(cStr, (CC_LONG)[data length], digest); // 'CC_MD5' is deprecated: first deprecated in iOS 13.0 - This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger). + //CC_MD5(cStr, (CC_LONG)[data length], digest); + +// static NSString *format = @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"; +// NSString *hash = [NSString stringWithFormat:format, digest[0], digest[1], +// digest[2], digest[3], +// digest[4], digest[5], +// digest[6], digest[7], +// digest[8], digest[9], +// digest[10], digest[11], +// digest[12], digest[13], +// digest[14], digest[15]]; + + const char* utf8chars = [data bytes]; + unsigned char result[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(utf8chars, (CC_LONG)strlen(utf8chars), result); + + NSMutableString *ret = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH*2]; + for(int i = 0; iCFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.iRare-Media.GameCenterManager + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -24,6 +24,8 @@ 5.3 LSRequiresIPhoneOS + UILaunchStoryboardName + Launch Screen.storyboard UIMainStoryboardFile Storyboard_iPhone UIPrerenderedIcon diff --git a/GameCenterManager/ViewController.m b/GameCenterManager/ViewController.m index 3208c63..e02522c 100755 --- a/GameCenterManager/ViewController.m +++ b/GameCenterManager/ViewController.m @@ -4,18 +4,43 @@ // // Created by iRare Media on Sepetmber 21, 2013. // Copyright (c) 2013 iRare Media. All rights reserved. -// +// Updated by Daniel Rosser 19/7/22 #import "ViewController.h" + +@interface ViewController() { + NSArray*gameleaderboardIDs; +} +@end + @implementation ViewController @synthesize scrollView; @synthesize statusDetailLabel, actionLabel, actionBarLabel; @synthesize playerPicture, playerName, playerStatus; + + //------------------------------------------------------------------------------------------------------------// //------- View Lifecycle -------------------------------------------------------------------------------------// //------------------------------------------------------------------------------------------------------------// + + +- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { + return UIRectEdgeAll; +} + +- (BOOL) prefersHomeIndicatorAutoHidden +{ + return YES; +} + +- (BOOL)canBecomeFirstResponder { + return YES; +} + + + #pragma mark - View Lifecycle - (void)viewDidLoad { @@ -29,6 +54,11 @@ - (void)viewDidLoad { // Set GameCenter Manager Delegate [[GameCenterManager sharedManager] setDelegate:self]; + + gameleaderboardIDs = [NSArray arrayWithObjects:[NSString stringWithUTF8String:@"grp.PlayerScores"], nil]; + + [[GameCenterManager sharedManager] setupManagerWithLeaderboardIDs:gameleaderboardIDs]; + [[GameCenterManager sharedManager] setupManagerAndSetShouldCryptWithKey:@"ChangeThisPass369"]; } - (void)viewWillAppear:(BOOL)animated { @@ -48,7 +78,7 @@ - (void)viewWillAppear:(BOOL)animated { playerName.text = player.displayName; playerStatus.text = @"Player is not underage"; [[GameCenterManager sharedManager] localPlayerPhoto:^(UIImage *playerPhoto) { - playerPicture.image = playerPhoto; + self->playerPicture.image = playerPhoto; }]; } else { playerName.text = player.displayName; @@ -60,17 +90,15 @@ - (void)viewWillAppear:(BOOL)animated { } } -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - return YES; -} - //------------------------------------------------------------------------------------------------------------// //------- GameCenter Scores ----------------------------------------------------------------------------------// //------------------------------------------------------------------------------------------------------------// #pragma mark - GameCenter Scores - (IBAction)reportScore { - [[GameCenterManager sharedManager] saveAndReportScore:[[GameCenterManager sharedManager] highScoreForLeaderboard:@"grp.PlayerScores"]+1 leaderboard:@"grp.PlayerScores" sortOrder:GameCenterSortOrderHighToLow]; + [[GameCenterManager sharedManager] saveAndReportScore:[[GameCenterManager sharedManager] highScoreForLeaderboard:@"grp.PlayerScores"]+1 + context:0 + leaderboard:@"grp.PlayerScores" sortOrder:GameCenterSortOrderHighToLow]; actionBarLabel.title = [NSString stringWithFormat:@"Score recorded."]; } @@ -165,7 +193,7 @@ - (void)gameCenterManager:(GameCenterManager *)manager availabilityChanged:(NSDi playerName.text = player.displayName; playerStatus.text = @"Player is not underage and is signed-in"; [[GameCenterManager sharedManager] localPlayerPhoto:^(UIImage *playerPhoto) { - playerPicture.image = playerPhoto; + self->playerPicture.image = playerPhoto; }]; } else { playerName.text = player.displayName; @@ -175,6 +203,19 @@ - (void)gameCenterManager:(GameCenterManager *)manager availabilityChanged:(NSDi } else { actionBarLabel.title = [NSString stringWithFormat:@"No GameCenter player found."]; } + + if([manager isGameCenterAvailable] == YES) { + NSLog(@"Game Center - SYNC"); + if(gameleaderboardIDs == NULL){ + gameleaderboardIDs = [NSArray arrayWithObjects:[NSString stringWithUTF8String:@"grp.PlayerScores"], nil]; + + } + + [[GameCenterManager sharedManager] setupManagerWithLeaderboardIDs:gameleaderboardIDs]; + [[GameCenterManager sharedManager] setupManagerAndSetShouldCryptWithKey:@"ChangeThisPass369"]; + + [[GameCenterManager sharedManager] syncGameCenter]; + } } - (void)gameCenterManager:(GameCenterManager *)manager error:(NSError *)error { @@ -200,6 +241,16 @@ - (void)gameCenterManager:(GameCenterManager *)manager reportedScore:(GKScore *) } } +- (void)gameCenterManager:(GameCenterManager *)manager reportedLeaderboardScore:(GKLeaderboardScore *)score withError:(NSError *)error API_AVAILABLE(ios(14.0)) { + + if(error == nil) + NSLog(@"GCM -reportedLeaderboardScore to Game Center"); + else + NSLog(@"GCM -reportedLeaderboardScore to Game Center WITH ERROR: %@", error); + + +} + - (void)gameCenterManager:(GameCenterManager *)manager didSaveScore:(GKScore *)score { NSLog(@"Saved GCM Score with value: %lld", score.value); actionBarLabel.title = [NSString stringWithFormat:@"Score saved for upload to GameCenter."]; @@ -210,6 +261,27 @@ - (void)gameCenterManager:(GameCenterManager *)manager didSaveAchievement:(GKAch actionBarLabel.title = [NSString stringWithFormat:@"Achievement saved for upload to GameCenter."]; } + + + + + + +- (void)gameCenterManager:(GameCenterManager *)manager didSaveLeaderboardScore:(GKLeaderboardScore *)score API_AVAILABLE(ios(14.0)) { + + NSLog(@"Saved GCM Score with value: %ld", (long)score.value); + actionBarLabel.title = [NSString stringWithFormat:@"Score saved for upload to GameCenter."]; + +} + + +-(void)gameCenterLogout { + [[GameCenterManager sharedManager] logout]; +} + + + + //------------------------------------------------------------------------------------------------------------// //------- UIActionSheet Delegate -----------------------------------------------------------------------------// //------------------------------------------------------------------------------------------------------------// diff --git a/Images.xcassets/AppIcon.appiconset/AppIcon29x29.png b/Images.xcassets/AppIcon.appiconset/AppIcon29x29.png deleted file mode 100755 index 8098537..0000000 Binary files a/Images.xcassets/AppIcon.appiconset/AppIcon29x29.png and /dev/null differ diff --git a/Images.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png b/Images.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png deleted file mode 100755 index 8dc8f6d..0000000 Binary files a/Images.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png and /dev/null differ diff --git a/Images.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png b/Images.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png deleted file mode 100644 index 065cb21..0000000 Binary files a/Images.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png and /dev/null differ diff --git a/Images.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png b/Images.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png deleted file mode 100755 index fc1e05c..0000000 Binary files a/Images.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png and /dev/null differ diff --git a/Images.xcassets/AppIcon.appiconset/Contents.json b/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100755 index ca19d05..0000000 --- a/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "AppIcon29x29.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "AppIcon29x29@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "AppIcon40x40@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "Icon.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "57x57" - }, - { - "filename" : "Icon@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "57x57" - }, - { - "filename" : "AppIcon60x60@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Images.xcassets/AppIcon.appiconset/Icon.png b/Images.xcassets/AppIcon.appiconset/Icon.png deleted file mode 100755 index bd8da2e..0000000 Binary files a/Images.xcassets/AppIcon.appiconset/Icon.png and /dev/null differ diff --git a/Images.xcassets/AppIcon.appiconset/Icon@2x.png b/Images.xcassets/AppIcon.appiconset/Icon@2x.png deleted file mode 100755 index 715341d..0000000 Binary files a/Images.xcassets/AppIcon.appiconset/Icon@2x.png and /dev/null differ diff --git a/Images.xcassets/Contents.json b/Images.xcassets/Contents.json index da4a164..73c0059 100644 --- a/Images.xcassets/Contents.json +++ b/Images.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Images.xcassets/GCSymbol.imageset/Contents.json b/Images.xcassets/GCSymbol.imageset/Contents.json index 509fa41..1b27384 100644 --- a/Images.xcassets/GCSymbol.imageset/Contents.json +++ b/Images.xcassets/GCSymbol.imageset/Contents.json @@ -6,17 +6,16 @@ }, { "idiom" : "universal", - "scale" : "2x", - "filename" : "GK Icon-512.png" + "scale" : "2x" }, { + "filename" : "Icon.png", "idiom" : "universal", - "scale" : "3x", - "filename" : "GK Icon-512@2x.png" + "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Images.xcassets/GCSymbol.imageset/GK Icon-512.png b/Images.xcassets/GCSymbol.imageset/GK Icon-512.png deleted file mode 100644 index 84a5ebe..0000000 Binary files a/Images.xcassets/GCSymbol.imageset/GK Icon-512.png and /dev/null differ diff --git a/Images.xcassets/GCSymbol.imageset/GK Icon-512@2x.png b/Images.xcassets/GCSymbol.imageset/GK Icon-512@2x.png deleted file mode 100644 index 2a4d561..0000000 Binary files a/Images.xcassets/GCSymbol.imageset/GK Icon-512@2x.png and /dev/null differ diff --git a/Images.xcassets/GCSymbol.imageset/Icon.png b/Images.xcassets/GCSymbol.imageset/Icon.png new file mode 100644 index 0000000..a8e77ec Binary files /dev/null and b/Images.xcassets/GCSymbol.imageset/Icon.png differ diff --git a/Images.xcassets/LaunchImage.launchimage/Contents.json b/Images.xcassets/LaunchImage.launchimage/Contents.json deleted file mode 100755 index 148feee..0000000 --- a/Images.xcassets/LaunchImage.launchimage/Contents.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "images" : [ - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "minimum-system-version" : "7.0", - "filename" : "Default@2x.png", - "scale" : "2x" - }, - { - "extent" : "full-screen", - "idiom" : "iphone", - "subtype" : "retina4", - "filename" : "Default-568h@2x.png", - "minimum-system-version" : "7.0", - "orientation" : "portrait", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "filename" : "Default.png", - "scale" : "1x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "filename" : "Default@2x-1.png", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "filename" : "Default-568h@2x.png", - "subtype" : "retina4", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png deleted file mode 100755 index 2775742..0000000 Binary files a/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png and /dev/null differ diff --git a/Images.xcassets/LaunchImage.launchimage/Default.png b/Images.xcassets/LaunchImage.launchimage/Default.png deleted file mode 100755 index f6c4263..0000000 Binary files a/Images.xcassets/LaunchImage.launchimage/Default.png and /dev/null differ diff --git a/Images.xcassets/LaunchImage.launchimage/Default@2x-1.png b/Images.xcassets/LaunchImage.launchimage/Default@2x-1.png deleted file mode 100644 index 679ac16..0000000 Binary files a/Images.xcassets/LaunchImage.launchimage/Default@2x-1.png and /dev/null differ diff --git a/Images.xcassets/LaunchImage.launchimage/Default@2x.png b/Images.xcassets/LaunchImage.launchimage/Default@2x.png deleted file mode 100755 index 679ac16..0000000 Binary files a/Images.xcassets/LaunchImage.launchimage/Default@2x.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/Contents.json b/Images.xcassets/MacAppIcon.appiconset/Contents.json index 366bee5..33be342 100755 --- a/Images.xcassets/MacAppIcon.appiconset/Contents.json +++ b/Images.xcassets/MacAppIcon.appiconset/Contents.json @@ -1,68 +1,59 @@ { "images" : [ { - "size" : "16x16", "idiom" : "mac", - "filename" : "GK Icon-16.png", - "scale" : "1x" + "scale" : "1x", + "size" : "16x16" }, { - "size" : "16x16", "idiom" : "mac", - "filename" : "GK Icon-16@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "16x16" }, { - "size" : "32x32", "idiom" : "mac", - "filename" : "GK Icon-32.png", - "scale" : "1x" + "scale" : "1x", + "size" : "32x32" }, { - "size" : "32x32", "idiom" : "mac", - "filename" : "GK Icon-32@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "32x32" }, { - "size" : "128x128", "idiom" : "mac", - "filename" : "GK Icon-128.png", - "scale" : "1x" + "scale" : "1x", + "size" : "128x128" }, { - "size" : "128x128", "idiom" : "mac", - "filename" : "GK Icon-128@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "128x128" }, { - "size" : "256x256", "idiom" : "mac", - "filename" : "GK Icon-256.png", - "scale" : "1x" + "scale" : "1x", + "size" : "256x256" }, { - "size" : "256x256", "idiom" : "mac", - "filename" : "GK Icon-256@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "256x256" }, { - "size" : "512x512", "idiom" : "mac", - "filename" : "GK Icon-512.png", - "scale" : "1x" + "scale" : "1x", + "size" : "512x512" }, { - "size" : "512x512", + "filename" : "Icon.png", "idiom" : "mac", - "filename" : "GK Icon-512@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "512x512" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Images.xcassets/MacAppIcon.appiconset/GK Icon-128.png b/Images.xcassets/MacAppIcon.appiconset/GK Icon-128.png deleted file mode 100755 index dd9c0a5..0000000 Binary files a/Images.xcassets/MacAppIcon.appiconset/GK Icon-128.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/GK Icon-128@2x.png b/Images.xcassets/MacAppIcon.appiconset/GK Icon-128@2x.png deleted file mode 100755 index 4ba7634..0000000 Binary files a/Images.xcassets/MacAppIcon.appiconset/GK Icon-128@2x.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/GK Icon-16.png b/Images.xcassets/MacAppIcon.appiconset/GK Icon-16.png deleted file mode 100755 index 1cdaff9..0000000 Binary files a/Images.xcassets/MacAppIcon.appiconset/GK Icon-16.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/GK Icon-16@2x.png b/Images.xcassets/MacAppIcon.appiconset/GK Icon-16@2x.png deleted file mode 100755 index ddceb65..0000000 Binary files a/Images.xcassets/MacAppIcon.appiconset/GK Icon-16@2x.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/GK Icon-256.png b/Images.xcassets/MacAppIcon.appiconset/GK Icon-256.png deleted file mode 100755 index 4ba7634..0000000 Binary files a/Images.xcassets/MacAppIcon.appiconset/GK Icon-256.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/GK Icon-256@2x.png b/Images.xcassets/MacAppIcon.appiconset/GK Icon-256@2x.png deleted file mode 100755 index 84a5ebe..0000000 Binary files a/Images.xcassets/MacAppIcon.appiconset/GK Icon-256@2x.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/GK Icon-32.png b/Images.xcassets/MacAppIcon.appiconset/GK Icon-32.png deleted file mode 100755 index ddceb65..0000000 Binary files a/Images.xcassets/MacAppIcon.appiconset/GK Icon-32.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/GK Icon-32@2x.png b/Images.xcassets/MacAppIcon.appiconset/GK Icon-32@2x.png deleted file mode 100755 index adad812..0000000 Binary files a/Images.xcassets/MacAppIcon.appiconset/GK Icon-32@2x.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/GK Icon-512.png b/Images.xcassets/MacAppIcon.appiconset/GK Icon-512.png deleted file mode 100755 index 84a5ebe..0000000 Binary files a/Images.xcassets/MacAppIcon.appiconset/GK Icon-512.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/GK Icon-512@2x.png b/Images.xcassets/MacAppIcon.appiconset/GK Icon-512@2x.png deleted file mode 100755 index 2a4d561..0000000 Binary files a/Images.xcassets/MacAppIcon.appiconset/GK Icon-512@2x.png and /dev/null differ diff --git a/Images.xcassets/MacAppIcon.appiconset/Icon.png b/Images.xcassets/MacAppIcon.appiconset/Icon.png new file mode 100644 index 0000000..a8e77ec Binary files /dev/null and b/Images.xcassets/MacAppIcon.appiconset/Icon.png differ diff --git a/Images.xcassets/iOSAppIcon.appiconset/Contents.json b/Images.xcassets/iOSAppIcon.appiconset/Contents.json new file mode 100644 index 0000000..b3f44eb --- /dev/null +++ b/Images.xcassets/iOSAppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "Icon.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/iOSAppIcon.appiconset/Icon.png b/Images.xcassets/iOSAppIcon.appiconset/Icon.png new file mode 100644 index 0000000..a8e77ec Binary files /dev/null and b/Images.xcassets/iOSAppIcon.appiconset/Icon.png differ diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Contents.json new file mode 100644 index 0000000..de59d88 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Contents.json new file mode 100644 index 0000000..de59d88 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/Contents.json new file mode 100644 index 0000000..f47ba43 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/Contents.json @@ -0,0 +1,32 @@ +{ + "assets" : [ + { + "filename" : "App Icon - App Store.imagestack", + "idiom" : "tv", + "role" : "primary-app-icon", + "size" : "1280x768" + }, + { + "filename" : "App Icon.imagestack", + "idiom" : "tv", + "role" : "primary-app-icon", + "size" : "400x240" + }, + { + "filename" : "Top Shelf Image Wide.imageset", + "idiom" : "tv", + "role" : "top-shelf-image-wide", + "size" : "2320x720" + }, + { + "filename" : "Top Shelf Image.imageset", + "idiom" : "tv", + "role" : "top-shelf-image", + "size" : "1920x720" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/Top Shelf Image Wide.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/Top Shelf Image Wide.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Brand Assets.brandassets/Top Shelf Image.imageset/Contents.json b/Images.xcassets/tvOS Brand Assets.brandassets/Top Shelf Image.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Brand Assets.brandassets/Top Shelf Image.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Leaderboard.gcleaderboard/Contents.json b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Back.imagestacklayer/Contents.json b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Contents.json b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Contents.json new file mode 100644 index 0000000..de59d88 --- /dev/null +++ b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Front.imagestacklayer/Contents.json b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..795cce1 --- /dev/null +++ b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Middle.imagestacklayer/Contents.json b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Images.xcassets/tvOS Leaderboard.gcleaderboard/Poster.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Images/GameBanner.png b/Images/GameBanner.png index bee831f..842a306 100644 Binary files a/Images/GameBanner.png and b/Images/GameBanner.png differ diff --git a/Images/GameBanner.psd b/Images/GameBanner.psd index 8eb9852..7b801e1 100644 Binary files a/Images/GameBanner.psd and b/Images/GameBanner.psd differ diff --git a/Images/Icon.png b/Images/Icon.png new file mode 100644 index 0000000..a8e77ec Binary files /dev/null and b/Images/Icon.png differ diff --git a/Launch Screen.storyboard b/Launch Screen.storyboard new file mode 100644 index 0000000..67d21f2 --- /dev/null +++ b/Launch Screen.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 20fe039..922e65c 100755 --- a/README.md +++ b/README.md @@ -1,43 +1,33 @@ -### A Note on Swift Conversion -*This branch of the project is currently being refactored and rewritten in Swift. Please contibute to help finish the conversion and improve the project. Once completed, the projct will switch over to Swift from Objective-C. Until that time, this branch is not ready for production use.* -GameCenter Manager helps to manage Game Center in iOS and Mac apps. Report and track high scores, achievements, and challenges for different players. GameCenter Manager also takes care of the heavy lifting - checking internet availability, saving data when offline and uploading it when online, etc. On iOS (and soon on other platforms) GameCenter Manager makes it easy to setup and run live peer-to-peer Game Center Multiplayer matches. +GameCenter Manager helps to manage Game Center in iOS, tvOS and macOS apps/games. +Report and track high scores, achievements, and challenges for different players. +GameCenter Manager also takes care of the heavy lifting - checking internet availability, saving data when offline and uploading it when online, etc. On iOS (and soon on other platforms) GameCenter Manager makes it easy to setup and run live peer-to-peer Game Center Multiplayer matches. -If you like the project, please [star it](https://github.com/nihalahmed/iCloudDocumentSync) on GitHub! Watch the project on GitHub for updates. If you use GameCenter Manager in your app, send an email to contact@iraremedia.com or let us know on Twitter @iRareMedia. # Project Features -GameCenter Manager is a great way to use Game Center in your iOS or OS X app. Below are a few key project features and highlights. +GameCenter Manager is a great way to use Game Center in your iOS, tvOS or macOS app. Below are a few key project features and highlights. * Sync, submit, save, retrieve, and track any Game Center leaderboards, achievements, or challenges in only one line of code. * Setup and manage peer-to-peer multiplayer Game Center matches * Just drag and drop the files into your project - no complicated setup * Useful delegate methods and properties let you access and manage advanced Game Center features -* iOS / OS X sample apps illustrate how easy it is to use GameCenter Manager +* iOS / macOS / tvOS sample apps illustrate how easy it is to use GameCenter Manager # Project Information Learn more about the project requirements, licensing, and contributions. ## Requirements * Built with and for Objective-C ARC -* Requires a minimum of iOS 7.0 / OS X 10.9 as the deployment target -* Requires Xcode 5.0.1 for use in any iOS Project -* Uses Apple LLVM compiler 5.0 +* Requires a minimum of iOS 12.0 / OS X 10.9 as the deployment target +* Requires Xcode 12.0.1 for use in any iOS Project ### Requirements -- Requires iOS 7.0+ or OS X 10.9+. The sample project is optimized for iOS 8 and OS X 10.10. +- Requires iOS 12.0+ or OS X 10.9+. The sample project is optimized for iOS 12 and OS X 10.10. - Requires Automatic Reference Counting (ARC). -- Optimized for ARM64 Architecture - -Requires Xcode 6 for use in any iOS or OS X project. Requires a minimum of iOS 7.0 or OS X 10.9 as the deployment target. - -| Current Build Target | Earliest Supported Build Target | Earliest Compatible Build Target | -|:--------------------: |:-------------------------------: |:--------------------------------: | -| iOS 9.0 | iOS 7.0 | iOS 7.0 | -| OS X 10.11 | OS X 10.9 | OS X 10.9 | -| Xcode 7.1 | Xcode 6.1.1 | Xcode 6.0 | -| LLVM 6.1 | LLVM 6.1 | LLVM 5.0 | +- Optimized for arm64 Architecture +- > REQUIREMENTS NOTE *Supported* means that the library has been tested with this version. *Compatible* means that the library should work on this OS version (i.e. it doesn't rely on any unavailable SDK features) but is no longer being tested for compatibility and may require tweaking or bug fixes to run correctly. @@ -54,7 +44,7 @@ GameCenter Manager's demo app makes it easier to test Game Center integration wi # Documentation -All methods, properties, types, and delegate methods available on the GameCenterManager class are documented below. If you're using [Xcode 5](https://developer.apple.com/technologies/tools/whats-new.html) with GameCenter Manager, documentation is available directly within Xcode (just Option-Click any method for Quick Help). +All methods, properties, types, and delegate methods available on the GameCenterManager class are documented below. If you're using [Xcode 12](https://developer.apple.com/technologies/tools/whats-new.html) with GameCenter Manager, documentation is available directly within Xcode (just Option-Click any method for Quick Help). ## Setup Setting up GameCenter Manager is very straightforward. These instructions do not detail how to enable Game Center in your app. You need to setup Game Center before using GameCenter Manager. Refer to the wiki pages for details on that. @@ -77,15 +67,19 @@ Setting up GameCenter Manager is very straightforward. These instructions do not There are many methods available on iCloud Document Sync. The most important / highlight methods are documented below. All other methods are documented in the docset and with in-code comments. ### Initialize GameCenterManager -You should setup GameCenterManager when your app is launched. This should only be done once and can be done in the `application: didFinishLaunchingWithOptions:` method of your AppDelegate. - [[GameCenterManager sharedManager] setupManager]; +Since iOS 14 you must pass in leaderboardID's you are using before setup storing used leaderboard ID's in your viewController may be a good idea: + NSArray* gameleaderboardIDs = [NSArray arrayWithObjects:[NSString stringWithUTF8String:@"grp.PlayerScores"], nil]; + [[GameCenterManager sharedManager] setupManagerWithLeaderboardIDs:gameleaderboardIDs]; +You should setup GameCenterManager when your app is launched - only once This initializes GameCenter Manager, checks if Game Center is supported on the current device, authenticates the player and synchronizes scores and achievements from Game Center. Alternatively, you can call the following method to enable encryption of local data: [[GameCenterManager sharedManager] setupManagerAndSetShouldCryptWithKey:@"YourKey"]; - -These methods are not interchangable. If you decide to setup with encryption then you should never revert to setting up without encryption, and vice versa. Doing so will cause issues with archiving and unarchiving the saved data - which results in a crash. Pick one and stick with it forever. If you do change it, you'll need to delete the `GameCenterManager.plist` file from your app's library (inside the bundle). + +Or without Encrypted scores - These methods are not interchangable. If you decide to setup with encryption then you should never revert to setting up without encryption + [[GameCenterManager sharedManager] setupManager]; + ### Check Game Center Support GameCenter Manager automatically checks if Game Center is available before performing any Game Center-related operations. You can also check for Game Center availability by using the following method, which returns a `BOOL` value (YES / NO). @@ -105,11 +99,12 @@ This method will perform the following checks in the following order: This method may return **NO** in many cases. Use the `gameCenterManager:availabilityChanged:` delegate method to get an `NSDictionary` containing information about why Game Center is or isn't available. Refer to the section on delegate methods below. ### Report Score -Report a score to Game Center using a Game Center Leaderboard ID. The score is saved locally then uploaded to Game Center (if Game Center is available). +Report a score to Game Center using a Game Center Leaderboard ID. The score is saved locally then uploaded to Game Center (if Game Center is available). - [[GameCenterManager sharedManager] saveAndReportScore:1000 leaderboard:@"Leaderboard ID" sortOrder:GameCenterSortOrder]; + [[GameCenterManager sharedManager] saveAndReportScore:1000 context:1 leaderboard:@"Leaderboard ID" sortOrder:GameCenterSortOrder]; Set the Game Center Sort Order (either `GameCenterSortOrderHighToLow` or `GameCenterSortOrderLowToHigh`) to report a score to Game Center only if the new score is better than the best one (depending on the sort order). There is no need for you to find out if a user has beat their highscore before submitting it - GameCenterManager will determine if the score should be submitted based on the parameters provided. +Context can be to assist anti cheaters ### Report Achievement Report an achievement to Game Center using a Game Center Achievement ID. The achievement and its percent complete are saved locally then uploaded to Game Center (if Game Center is available). @@ -233,16 +228,18 @@ The `error` NSError object contains an error code (refer to the section on Const ### Reported Score Called after the submitted score is successfully saved, uploaded, and posted to Game Center. -The GKScore object, `score`, is the final score that was saved. The error object may contain an error if one occured, or it may be nil. - - (void)gameCenterManager:(GameCenterManager *)manager reportedScore:(GKScore *)score withError:(NSError *)error; +The GKLeaderboardScore object, `score`, is the final score that was saved. The error object may contain an error if one occured, or it may be nil. + - (void)gameCenterManager:(GameCenterManager *)manager reportedLeaderboardScore:(GKLeaderboardScore *)score withError:(NSError *)error; ### Saved Score Called after the submitted score is successfully saved, but not posted or uploaded to Game Center. The saved score will be uploaded the next time GC Manager can successfully connect to Game Center. -The GKScore object, `score` contains information about the submitted score. +The GKLeaderboardScore object, `score` contains information about the submitted score. + + - (void)gameCenterManager:(GameCenterManager *)manager didSaveLeaderboardScore:(GKLeaderboardScore *)score + - - (void)gameCenterManager:(GameCenterManager *)manager didSaveScore:(GKScore *)score ### Reported Achievement Called after the submitted achievement and its percent complete is successfully saved, uploaded, and posted to Game Center. @@ -273,3 +270,5 @@ When the `gameCenterManager: error:` delegate is called, one of the following er - `GCMErrorFeatureNotAvailable` (2) the request feature is not available, check error message for info - `GCMErrorInternetNotAvailable` (3) no internet connection - `GCMErrorAchievementDataMissing` (3) could not save achievement because the data was formatted improperly or is missing + +If you like the project, please [star it](https://github.com/nihalahmed/iCloudDocumentSync) on GitHub! Watch the project on GitHub for updates. If you use GameCenter Manager in your app, send an email to contact@iraremedia.com or let us know on Twitter @iRareMedia.