diff --git a/GC Manager/GameCenterManager.h b/GC Manager/GameCenterManager.h new file mode 100755 index 0000000..ad5b1a3 --- /dev/null +++ b/GC Manager/GameCenterManager.h @@ -0,0 +1,196 @@ +// +// 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. +// + +#warning Definition of GameCenterManagerKey is required. Change this value to your own secret key. +#define kGameCenterManagerKey [@"MyKey" dataUsingEncoding:NSUTF8StringEncoding] + +#define LIBRARY_FOLDER [NSHomeDirectory() stringByAppendingPathComponent:@"Library"] +#define kGameCenterManagerDataFile @"GameCenterManager.plist" +#define kGameCenterManagerDataPath [LIBRARY_FOLDER stringByAppendingPathComponent:kGameCenterManagerDataFile] + +#import +#import +#import "Reachability.h" +#import "NSDataAES256.h" + +/// 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 + GameCenterSortOrderHighToLow, + /// Scores are sorted lowest to highest. Lower scores are on the top of the leaderboard + GameCenterSortOrderLowToHigh +} GameCenterSortOrder; + +enum { + /// An unknown error occurred + GCMErrorUnknown = 1, + /// GameCenterManager is unavailable, possibly for a variety of reasons + GCMErrorNotAvailable = 2, + /// The requested feature is unavailable on the current device or iOS version + GCMErrorFeatureNotAvailable = 3, + /// There is no active internet connection for the requested operation + GCMErrorInternetNotAvailable = 4, + /// The achievement data submitted was not valid because there were missing parameters + GCMErrorAchievementDataMissing = 5 +}; +/// GameCenterManager error codes that may be passed in a completion handler's error parameter +typedef NSInteger GCMErrorCode; + +/// 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; +@interface GameCenterManager : NSObject { + NSMutableArray *GCMLeaderboards; + #if TARGET_OS_IPHONE + UIBackgroundTaskIdentifier backgroundProcess; + #endif +} + +/// GameCenterManager delegate property that can be used to set the delegate +@property (nonatomic, weak) id delegate; + +/// Returns the shared instance of GameCenterManager. ++ (GameCenterManager *)sharedManager; + + + +/// Initializes Game Center Manager. Should be called at app launch. +- (void)initGameCenter; + +/// Synchronizes local player data with Game Center data. +- (void)syncGameCenter; + + + +/** Saves score locally and reports it to Game Center. If error occurs, score is saved to be submitted later. + + @param score The int value of the score to be submitted to Game Center. This score should not be formatted, instead it should be a plain 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:(int)score 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. + + @param identifier The Achievement ID set through iTunes Connect. This is different from the name of the achievement, and it is not shown to the user. + @param percentComplete A percentage value that states how far the player has progressed on this achievement. The range of legal values is between 0.0 and 100.0. Submitting 100.0 will mark the achievement as completed. Submitting a percent which is lower than what the user has already achieved will be ignored - the user's achievement progress cannot go down. + @param displayNotification YES if GCManager should display a Game Center Achievement banner. NO if no banner should be displayed */ +- (void)saveAndReportAchievement:(NSString *)identifier percentComplete:(double)percentComplete shouldDisplayNotification:(BOOL)displayNotification __attribute__((nonnull)); + + + +/// Reports scores and achievements which could not be reported earlier. +- (void)reportSavedScoresAndAchievements; + +/// Saves score to be submitted later. +- (void)saveScoreToReportLater:(GKScore *)score; + +/// Saves achievement to be submitted later. +- (void)saveAchievementToReportLater:(NSString *)identifier percentComplete:(double)percentComplete; + + + +/// Returns local player's high score for specified leaderboard. +- (int)highScoreForLeaderboard:(NSString *)identifier; + +/// Returns local player's high scores for multiple leaderboards. +- (NSDictionary *)highScoreForLeaderboards:(NSArray *)identifiers; + + + +/// Returns local player's percent completed for specified achievement. +- (double)progressForAchievement:(NSString *)identifier; + +/// Returns local player's percent completed for multiple achievements. +- (NSDictionary *)progressForAchievements:(NSArray *)identifiers; + + + +/** Gets a list of challenges for the current player and game. If GameCenter is not available it will return nil and provide an error using the gameCenterManager:error: delegate method. Use the completion handler to get challenges. + @param handler Completion handler with an NSArray containing challenges and an NSError. The NSError object will be nil if there is no error. */ +- (void)getChallengesWithCompletion:(void (^)(NSArray *challenges, NSError *error))handler __attribute__((nonnull)); + + +#if TARGET_OS_IPHONE +/// Resets all of the local player's achievements and progress for the current game +- (void)resetAchievementsWithCompletion:(void (^)(NSError *error))handler __attribute__((nonnull)); +#else +/// Resets all of the local player's achievements and progress for the current game +- (void)resetAchievementsWithCompletion:(void (^)(NSError *error))handler __attribute__((nonnull)); + +/// DEPRECATED. Use resetAchievementsWithCompletion: instead +- (void)resetAchievements __deprecated __unavailable; +#endif + + +/// Returns currently authenticated local player ID. If no player is authenticated, "unknownPlayer" is returned. +- (NSString *)localPlayerId; + +/// Returns currently authenticated local player's display name (alias or actual name depending on friendship). If no player is authenticated, "unknownPlayer" is returned. Player Alias will be returned if the Display Name property is not available +- (NSString *)localPlayerDisplayName; + +/// Returns currently authenticated local player and all associated data. If no player is authenticated, `nil` is returned. +- (GKLocalPlayer *)localPlayerData; + +#if TARGET_OS_IPHONE +/// Fetches a UIImage with the local player's profile picture at full resolution. The completion handler passes a UIImage object when the image is downloaded from the GameCenter Servers +- (void)localPlayerPhoto:(void (^)(UIImage *playerPhoto))handler __attribute__((nonnull)) __OSX_AVAILABLE_STARTING(__OSX_10_8,__IPHONE_5_0); +#else +/// Fetches an NSImage with the local player's profile picture at full resolution. The completion handler passes an NSImage object when the image is downloaded from the GameCenter Servers +- (void)localPlayerPhoto:(void (^)(NSImage *playerPhoto))handler __attribute__((nonnull)); +#endif + + + +/// Returns YES if an active internet connection is available. +- (BOOL)isInternetAvailable; + +/// Check if GameCenter is supported +- (BOOL)checkGameCenterAvailability; + +/// Use this property to check if Game Center is available and supported on the current device. +@property (nonatomic, assign) BOOL isGameCenterAvailable; + + +@end + + +/// GameCenterManager Delegate. Used for deeper control of the GameCenterManager class - allows for notification subscription, error reporting, and availability handling. +@protocol GameCenterManagerDelegate + +#if TARGET_OS_IPHONE +@required +/// Required Delegate Method called when the user needs to be authenticated using the GameCenter Login View Controller +- (void)gameCenterManager:(GameCenterManager *)manager authenticateUser:(UIViewController *)gameCenterLoginController; +#endif + +@optional +/// Delegate Method called when the availability of GameCenter changes +- (void)gameCenterManager:(GameCenterManager *)manager availabilityChanged:(NSDictionary *)availabilityInformation; + +/// Delegate Method called when the there is an error with GameCenter or GC Manager +- (void)gameCenterManager:(GameCenterManager *)manager error:(NSError *)error; + +/// Sent to the delegate when a score is reported to GameCenter +- (void)gameCenterManager:(GameCenterManager *)manager reportedScore:(GKScore *)score withError:(NSError *)error; +/// Sent to the delegate when an achievement is reported to GameCenter +- (void)gameCenterManager:(GameCenterManager *)manager reportedAchievement:(GKAchievement *)achievement withError:(NSError *)error; + +/// Sent to the delegate when an achievement is saved locally +- (void)gameCenterManager:(GameCenterManager *)manager didSaveAchievement:(GKAchievement *)achievement; +/// Sent to the delegate when a score is saved locally +- (void)gameCenterManager:(GameCenterManager *)manager didSaveScore:(GKScore *)score; + + +- (void)gameCenterManager:(GameCenterManager *)manager savedScore:(GKScore *)score __deprecated; +- (void)gameCenterManager:(GameCenterManager *)manager savedAchievement:(NSDictionary *)achievementInformation __deprecated; +- (void)gameCenterManager:(GameCenterManager *)manager reportedScore:(NSDictionary *)scoreInformation __deprecated; +- (void)gameCenterManager:(GameCenterManager *)manager reportedAchievement:(NSDictionary *)achievementInformation __deprecated; +- (void)gameCenterManager:(GameCenterManager *)manager resetAchievements:(NSError *)error __deprecated __unavailable; + +@end + + diff --git a/GC Manager/GameCenterManager.m b/GC Manager/GameCenterManager.m new file mode 100755 index 0000000..e920cb2 --- /dev/null +++ b/GC Manager/GameCenterManager.m @@ -0,0 +1,966 @@ +// +// GameCenterManager.m +// +// Created by Nihal Ahmed on 12-03-16. Updated by iRare Media on 5-27-13. +// Copyright (c) 2012 NABZ Software. All rights reserved. +// + +#import "GameCenterManager.h" + + +//------------------------------------------------------------------------------------------------------------// +//------- GameCenter Manager Singleton -----------------------------------------------------------------------// +//------------------------------------------------------------------------------------------------------------// +#pragma mark GameCenter Manager + +@implementation GameCenterManager +@synthesize isGameCenterAvailable, delegate; + ++ (GameCenterManager *)sharedManager { + static GameCenterManager *sharedManager = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + if (sharedManager == nil) { + sharedManager = [[super allocWithZone:NULL] init]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:kGameCenterManagerDataPath]) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:dict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + } + + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + if (gameCenterManagerData == nil) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:dict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + }); + + return sharedManager; +} + ++ (id)allocWithZone:(NSZone *)zone { + return [self sharedManager]; +} + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +//------------------------------------------------------------------------------------------------------------// +//------- GameCenter Manager Setup ---------------------------------------------------------------------------// +//------------------------------------------------------------------------------------------------------------// +#pragma mark - GameCenter Manager Setup + +- (void)initGameCenter { + BOOL gameCenterAvailable = [self checkGameCenterAvailability]; + 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]; + } + + } else { + [self setIsGameCenterAvailable:NO]; + } +} + +- (BOOL)checkGameCenterAvailability { +#if TARGET_OS_IPHONE + // First, check if the the GameKit Framework exists on the device. Return NO if it does not. + BOOL localPlayerClassAvailable = (NSClassFromString(@"GKLocalPlayer")) != nil; + NSString *reqSysVer = @"4.1"; + NSString *currSysVer = [[UIDevice currentDevice] systemVersion]; + BOOL osVersionSupported = ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending); + BOOL isGameCenterAPIAvailable = (localPlayerClassAvailable && osVersionSupported); +#else + BOOL isGameCenterAPIAvailable = (NSClassFromString(@"GKLocalPlayer")) != nil; +#endif + + if (!isGameCenterAPIAvailable) { + NSDictionary *errorDictionary = @{@"message": @"GameKit Framework not available on this device. GameKit is only available on devices with iOS 4.1 or higher. Some devices running iOS 4.1 may not have GameCenter enabled.", @"title": @"GameCenter Unavailable"}; + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:availabilityChanged:)]) + [[self delegate] gameCenterManager:self availabilityChanged:errorDictionary]; + }); + + return NO; + + } else { + // The GameKit Framework is available. Now check if an internet connection can be established + BOOL internetAvailable = [self isInternetAvailable]; + if (!internetAvailable) { + 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]; + }); + + return NO; + + } 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) { + 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]; + } + }; +#else + localPlayer.authenticateHandler = ^(NSViewController *viewController, NSError *error) { + if (viewController != nil) { + 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]; + }); + } else if (!error) { + // Authentication handler completed successfully. Re-check availability + [self checkGameCenterAvailability]; + } + }; +#endif + + if (![[GKLocalPlayer localPlayer] isAuthenticated]) { + 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]; + + return NO; + + } else { + // The current player is logged into GameCenter + NSDictionary *successDictionary = [NSDictionary dictionaryWithObject:@"GameCenter Available" forKey:@"status"]; + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:availabilityChanged:)]) + [[self delegate] gameCenterManager:self availabilityChanged:successDictionary]; + }); + + self.isGameCenterAvailable = YES; + + return YES; + } + } + } +} + +// Check for internet with Reachability +- (BOOL)isInternetAvailable { + Reachability *reachability = [Reachability reachabilityForInternetConnection]; + NetworkStatus internetStatus = [reachability currentReachabilityStatus]; + + if (internetStatus == NotReachable) { + NSError *error = [NSError errorWithDomain:[NSString stringWithFormat:@"Internet unavailable - could not connect to the internet. Connect to WiFi or a Cellular Network to upload data to GameCenter."] code:GCMErrorInternetNotAvailable userInfo:nil]; + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + + return NO; + } else { + return YES; + } +} + +//------------------------------------------------------------------------------------------------------------// +//------- GameCenter Syncing ---------------------------------------------------------------------------------// +//------------------------------------------------------------------------------------------------------------// +#pragma mark - GameCenter Syncing + +- (void)syncGameCenter { +#if TARGET_OS_IPHONE + // 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, ^{ + + // Check if GameCenter is available + if ([self checkGameCenterAvailability] == 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]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; + return; + } + + + if (GCMLeaderboards.count > 0) { + GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] initWithPlayerIDs:[NSArray arrayWithObject:[self localPlayerId]]]; + + if ([[GKLeaderboard alloc] respondsToSelector:@selector(identifier)]) { + [leaderboardRequest setIdentifier:[(GKLeaderboard *)[GCMLeaderboards objectAtIndex:0] identifier]]; + } else { + [leaderboardRequest setCategory:[(GKLeaderboard *)[GCMLeaderboards objectAtIndex:0] category]]; + } + + [leaderboardRequest loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) { + if (error == nil) { + if (scores.count > 0) { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict == nil) { + playerDict = [NSMutableDictionary dictionary]; + } + + float savedHighScoreValue = 0; + NSNumber *savedHighScore = [playerDict objectForKey:leaderboardRequest.localPlayerScore.category]; + + if (savedHighScore != nil) { + savedHighScoreValue = [savedHighScore intValue]; + } + + [playerDict setObject:[NSNumber numberWithInt:MAX(leaderboardRequest.localPlayerScore.value, savedHighScoreValue)] forKey:leaderboardRequest.localPlayerScore.category]; + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + + // 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]; + } + + [self syncGameCenter]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; + } else { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]; + [self syncGameCenter]; + } + + + // Check if Achievements are synced + } else if (![[NSUserDefaults standardUserDefaults] boolForKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]) { + [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) { + if (error == nil) { + NSLog(@"Number of Achievements: %@", achievements); + if (achievements.count > 0) { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict == nil) { + playerDict = [NSMutableDictionary dictionary]; + } + + for (GKAchievement *achievement in achievements) { + [playerDict setObject:[NSNumber numberWithDouble:achievement.percentComplete] forKey:achievement.identifier]; + } + + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + + } + + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + [self syncGameCenter]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; + } + } else { + NSError *error = [NSError errorWithDomain:[NSString stringWithFormat:@"GameCenter unavailable."] code:GCMErrorNotAvailable userInfo:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }); + + // End the Background Process + [[UIApplication sharedApplication] endBackgroundTask:backgroundProcess]; + backgroundProcess = UIBackgroundTaskInvalid; +#else + // Check if GameCenter is available + if ([self checkGameCenterAvailability] == 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]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; + return; + } + + if (GCMLeaderboards.count > 0) { + GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] initWithPlayerIDs:[NSArray arrayWithObject:[self localPlayerId]]]; + [leaderboardRequest setCategory:[(GKLeaderboard *)[GCMLeaderboards objectAtIndex:0] category]]; + [leaderboardRequest loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) { + if (error == nil) { + if (scores.count > 0) { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict == nil) { + playerDict = [NSMutableDictionary dictionary]; + } + + float savedHighScoreValue = 0; + NSNumber *savedHighScore = [playerDict objectForKey:leaderboardRequest.localPlayerScore.category]; + + if (savedHighScore != nil) { + savedHighScoreValue = [savedHighScore intValue]; + } + + [playerDict setObject:[NSNumber numberWithInt:MAX(leaderboardRequest.localPlayerScore.value, savedHighScoreValue)] forKey:leaderboardRequest.localPlayerScore.category]; + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + + // 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]; + } + + [self syncGameCenter]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; + } else { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:[@"scoresSynced" stringByAppendingString:[self localPlayerId]]]; + [self syncGameCenter]; + } + + // Check if Achievements are synced + } else if (![[NSUserDefaults standardUserDefaults] boolForKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]) { + [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) { + if (error == nil) { + if (achievements.count > 0) { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict == nil) { + playerDict = [NSMutableDictionary dictionary]; + } + + for (GKAchievement *achievement in achievements) { + [playerDict setObject:[NSNumber numberWithDouble:achievement.percentComplete] forKey:achievement.identifier]; + } + + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + [self syncGameCenter]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; + } + } else { + NSError *error = [NSError errorWithDomain:[NSString stringWithFormat:@"GameCenter unavailable."] code:GCMErrorNotAvailable userInfo:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } +#endif +} + +- (void)reportSavedScoresAndAchievements { + if ([self isInternetAvailable] == NO) return; + + GKScore *gkScore = nil; + + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableArray *savedScores = [plistDict objectForKey:@"SavedScores"]; + + if (savedScores != nil) { + if (savedScores.count > 0) { + gkScore = [NSKeyedUnarchiver unarchiveObjectWithData:[savedScores objectAtIndex:0]]; + + [savedScores removeObjectAtIndex:0]; + [plistDict setObject:savedScores forKey:@"SavedScores"]; + + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + } + + if (gkScore != nil) { + [gkScore reportScoreWithCompletionHandler:^(NSError *error) { + if (error == nil) { + [self reportSavedScoresAndAchievements]; + } else { + [self saveScoreToReportLater:gkScore]; + } + }]; + } else { + if ([GKLocalPlayer localPlayer].authenticated) { + NSString *identifier = nil; + double percentComplete = 0; + + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict != nil) { + NSMutableDictionary *savedAchievements = [playerDict objectForKey:@"SavedAchievements"]; + 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"]; + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + } + } + + if (identifier != nil) { + GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:identifier]; + achievement.percentComplete = percentComplete; + [achievement reportAchievementWithCompletionHandler:^(NSError *error) { + if (error == nil) { + [self reportSavedScoresAndAchievements]; + } else { + [self saveAchievementToReportLater:achievement.identifier percentComplete:achievement.percentComplete]; + } + }]; + + } + } + } +} + + +//------------------------------------------------------------------------------------------------------------// +//------- Score and Achievement Reporting --------------------------------------------------------------------// +//------------------------------------------------------------------------------------------------------------// +#pragma mark - Score and Achievement Reporting + +- (void)saveAndReportScore:(int)score leaderboard:(NSString *)identifier sortOrder:(GameCenterSortOrder)order { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict == nil) { + playerDict = [NSMutableDictionary dictionary]; + } + + NSNumber *savedHighScore = [playerDict objectForKey:identifier]; + if (savedHighScore == nil) { + savedHighScore = [NSNumber numberWithInt:0]; + } + + int savedHighScoreValue = [savedHighScore intValue]; + + // Determine if the new score is better than the old score + BOOL isScoreBetter = NO; + switch (order) { + case GameCenterSortOrderLowToHigh: // A lower score is better + if (score < savedHighScoreValue) { + isScoreBetter = YES; + } + break; + default: + if (score > savedHighScoreValue) { // A higher score is better + isScoreBetter = YES; + } + break; + } + + if (isScoreBetter) { + [playerDict setObject:[NSNumber numberWithInt:score] forKey:identifier]; + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + + if ([self checkGameCenterAvailability] == YES) { + GKScore *gkScore = [[GKScore alloc] initWithCategory:identifier]; + gkScore.value = score; + + [gkScore reportScoreWithCompletionHandler:^(NSError *error) { + NSDictionary *dict = nil; + + if (error == nil) { + dict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:gkScore, nil] forKeys:[NSArray arrayWithObjects:@"score", nil]]; + } else { + dict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:error.localizedDescription, gkScore, nil] forKeys:[NSArray arrayWithObjects:@"error", @"score", nil]]; + [self saveScoreToReportLater:gkScore]; + } + + 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]; + }); + }]; + + } else { + GKScore *gkScore = [[GKScore alloc] initWithCategory:identifier]; + [self saveScoreToReportLater:gkScore]; + } +} + +- (void)saveAndReportAchievement:(NSString *)identifier percentComplete:(double)percentComplete shouldDisplayNotification:(BOOL)displayNotification { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict == nil) + playerDict = [NSMutableDictionary dictionary]; + + NSNumber *savedPercentComplete = [playerDict objectForKey:identifier]; + if (savedPercentComplete == nil) + savedPercentComplete = [NSNumber numberWithDouble:0]; + + double savedPercentCompleteValue = [savedPercentComplete doubleValue]; + if (percentComplete > savedPercentCompleteValue) { + [playerDict setObject:[NSNumber numberWithDouble:percentComplete] forKey:identifier]; + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + } + + if ([self checkGameCenterAvailability] == YES) { + GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:identifier]; + achievement.percentComplete = percentComplete; + if (displayNotification == YES) { + achievement.showsCompletionBanner = YES; + } else { + achievement.showsCompletionBanner = NO; + } + + [achievement reportAchievementWithCompletionHandler:^(NSError *error) { + NSDictionary *dict = nil; + + if (error == nil) { + dict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:achievement, nil] forKeys:[NSArray arrayWithObjects:@"achievement", nil]]; + } else { + if (achievement) { + dict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:error.localizedDescription, achievement, nil] forKeys:[NSArray arrayWithObjects:@"error", @"achievement", nil]]; + } + + [self saveAchievementToReportLater:identifier percentComplete:percentComplete]; + } + + 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]; + }); + + }]; + + } else { + [self saveAchievementToReportLater:identifier percentComplete:percentComplete]; + } +} + +- (void)saveScoreToReportLater:(GKScore *)score { + NSData *scoreData = [NSKeyedArchiver archivedDataWithRootObject:score]; + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableArray *savedScores = [plistDict objectForKey:@"SavedScores"]; + + if (savedScores != nil) { + [savedScores addObject:scoreData]; + } else { + savedScores = [NSMutableArray arrayWithObject:scoreData]; + } + + [plistDict setObject:savedScores forKey:@"SavedScores"]; + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + + dispatch_async(dispatch_get_main_queue(), ^{ + 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 + } else if ([[self delegate] respondsToSelector:@selector(gameCenterManager:didSaveScore:)]) + [[self delegate] gameCenterManager:self didSaveScore:score]; + }); +} + +- (void)saveAchievementToReportLater:(NSString *)identifier percentComplete:(double)percentComplete { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict != nil) { + NSMutableDictionary *savedAchievements = [playerDict objectForKey:@"SavedAchievements"]; + if (savedAchievements != nil) { + double savedPercentCompleteValue = 0; + NSNumber *savedPercentComplete = [savedAchievements objectForKey:identifier]; + + if (savedPercentComplete != nil) { + savedPercentCompleteValue = [savedPercentComplete doubleValue]; + } + + // Compare the saved percent and the percent that was just submitted, if the submitted percent is greater save it + if (percentComplete > savedPercentCompleteValue) { + savedPercentComplete = [NSNumber numberWithDouble:percentComplete]; + [savedAchievements setObject:savedPercentComplete forKey:identifier]; + } + } else { + savedAchievements = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithDouble:percentComplete], identifier, nil]; + [playerDict setObject:savedAchievements forKey:@"SavedAchievements"]; + } + } else { + NSMutableDictionary *savedAchievements = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithDouble:percentComplete], identifier, nil]; + playerDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:savedAchievements, @"SavedAchievements", nil]; + } + + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + + GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:identifier]; + NSNumber *percentNumber = [NSNumber numberWithDouble:percentComplete]; + + if (percentNumber && achievement) { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:savedAchievement:)]) { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + [[self delegate] gameCenterManager:self savedAchievement:[NSDictionary dictionaryWithObjects:@[achievement, percentNumber] forKeys:@[@"achievement", @"percent complete"]]]; + #pragma clang diagnostic pop + } else if ([[self delegate] respondsToSelector:@selector(gameCenterManager:didSaveAchievement:)]) + [[self delegate] gameCenterManager:self didSaveAchievement:achievement]; + }); + } else { + NSError *error = [NSError errorWithDomain:[NSString stringWithFormat:@"Could not save achievement because necessary data is missing. GameCenter needs an Achievement ID and Percent Completed to save the achievement. You provided the following data:\nAchievement: %@\nPercent Completed:%@", achievement, percentNumber] + code:GCMErrorAchievementDataMissing userInfo:nil]; + + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } +} + +//------------------------------------------------------------------------------------------------------------// +//------- Score, Achievement, and Challenge Retrieval --------------------------------------------------------// +//------------------------------------------------------------------------------------------------------------// +#pragma mark - Score, Achievement, and Challenge Retrieval + +- (int)highScoreForLeaderboard:(NSString *)identifier { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict != nil) { + NSNumber *savedHighScore = [playerDict objectForKey:identifier]; + if (savedHighScore != nil) { + return [savedHighScore intValue]; + } else { + return 0; + } + } else { + return 0; + } +} + +- (NSDictionary *)highScoreForLeaderboards:(NSArray *)identifiers { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + NSMutableDictionary *highScores = [[NSMutableDictionary alloc] initWithCapacity:identifiers.count]; + + for (NSString *identifier in identifiers) { + if (playerDict != nil) { + NSNumber *savedHighScore = [playerDict objectForKey:identifier]; + + if (savedHighScore != nil) { + [highScores setObject:[NSNumber numberWithInt:[savedHighScore intValue]] forKey:identifier]; + continue; + } + } + + [highScores setObject:[NSNumber numberWithInt:0] forKey:identifier]; + } + + NSDictionary *highScoreDict = [NSDictionary dictionaryWithDictionary:highScores]; + + return highScoreDict; +} + +- (double)progressForAchievement:(NSString *)identifier { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict != nil) { + NSNumber *savedPercentComplete = [playerDict objectForKey:identifier]; + + if (savedPercentComplete != nil) { + return [savedPercentComplete doubleValue]; + } + } + return 0; +} + +- (NSDictionary *)progressForAchievements:(NSArray *)identifiers { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + NSMutableDictionary *percent = [[NSMutableDictionary alloc] initWithCapacity:identifiers.count]; + + for (NSString *identifier in identifiers) { + if (playerDict != nil) { + NSNumber *savedPercentComplete = [playerDict objectForKey:identifier]; + + if (savedPercentComplete != nil) { + [percent setObject:[NSNumber numberWithDouble:[savedPercentComplete doubleValue]] forKey:identifier]; + continue; + } + } + + [percent setObject:[NSNumber numberWithDouble:0] forKey:identifier]; + } + + NSDictionary *percentDict = [NSDictionary dictionaryWithDictionary:percent]; + + return percentDict; +} + +- (void)getChallengesWithCompletion:(void (^)(NSArray *challenges, NSError *error))handler { + if ([self checkGameCenterAvailability] == YES) { + BOOL isGameCenterChallengeAPIAvailable = (NSClassFromString(@"GKChallenge")) != nil; + + if (isGameCenterChallengeAPIAvailable == YES) { + [GKChallenge loadReceivedChallengesWithCompletionHandler:^(NSArray *challenges, NSError *error) { + if (error == nil) { + handler(challenges, nil); + } else { + handler(nil, error); + } + }]; + } else { +#if TARGET_OS_IPHONE + NSError *error = [NSError errorWithDomain:[NSString stringWithFormat:@"GKChallenge Class is not available. GKChallenge is only available on iOS 6.0 and higher. Current iOS version: %@", [[UIDevice currentDevice] systemVersion]] code:GCMErrorFeatureNotAvailable userInfo:nil]; +#else + NSError *error = [NSError errorWithDomain:[NSString stringWithFormat:@"GKChallenge Class is not available. GKChallenge is only available on OS X 10.8.2 and higher."] code:GCMErrorFeatureNotAvailable userInfo:nil]; +#endif + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + } + } else { + NSError *error = [NSError errorWithDomain:[NSString stringWithFormat:@"GameCenter Unavailable"] code:GCMErrorNotAvailable userInfo:nil]; + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + } +} + +//------------------------------------------------------------------------------------------------------------// +//------- Resetting Data -------------------------------------------------------------------------------------// +//------------------------------------------------------------------------------------------------------------// +#pragma mark - Resetting Data + +- (void)resetAchievementsWithCompletion:(void (^)(NSError *))handler { + if ([self isGameCenterAvailable]) { + [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) { + if (error == nil) { + NSData *gameCenterManagerData = [[NSData dataWithContentsOfFile:kGameCenterManagerDataPath] decryptedWithKey:kGameCenterManagerKey]; + NSMutableDictionary *plistDict = [NSKeyedUnarchiver unarchiveObjectWithData:gameCenterManagerData]; + NSMutableDictionary *playerDict = [plistDict objectForKey:[self localPlayerId]]; + + if (playerDict == nil) { + playerDict = [NSMutableDictionary dictionary]; + } + + for (GKAchievement *achievement in achievements) { + [playerDict removeObjectForKey:achievement.identifier]; + } + + [plistDict setObject:playerDict forKey:[self localPlayerId]]; + NSData *saveData = [[NSKeyedArchiver archivedDataWithRootObject:plistDict] encryptedWithKey:kGameCenterManagerKey]; + [saveData writeToFile:kGameCenterManagerDataPath atomically:YES]; + + [GKAchievement resetAchievementsWithCompletionHandler:^(NSError *error) { + if (error == nil) { + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:[@"achievementsSynced" stringByAppendingString:[self localPlayerId]]]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + [self syncGameCenter]; + + dispatch_async(dispatch_get_main_queue(), ^{ + handler(nil); + }); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + handler(error); + }); + } + }]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + handler(error); + }); + } + }]; + } +} + +//------------------------------------------------------------------------------------------------------------// +//------- Player Data ----------------------------------------------------------------------------------------// +//------------------------------------------------------------------------------------------------------------// +#pragma mark - Player Data + +- (NSString *)localPlayerId { + if ([self isGameCenterAvailable]) { + if ([GKLocalPlayer localPlayer].authenticated) { + return [GKLocalPlayer localPlayer].playerID; + } + } + return @"unknownPlayer"; +} + +- (NSString *)localPlayerDisplayName { + if ([self isGameCenterAvailable] && [GKLocalPlayer localPlayer].authenticated) { + if ([[GKLocalPlayer localPlayer] respondsToSelector:@selector(displayName)]) { + return [GKLocalPlayer localPlayer].displayName; + } else { + return [GKLocalPlayer localPlayer].alias; + } + } + + return @"unknownPlayer"; +} + +- (GKLocalPlayer *)localPlayerData { + if ([self isGameCenterAvailable] && [GKLocalPlayer localPlayer].authenticated) { + return [GKLocalPlayer localPlayer]; + } else { + return nil; + } +} + +#if TARGET_OS_IPHONE +- (void)localPlayerPhoto:(void (^)(UIImage *playerPhoto))handler { + if ([self isGameCenterAvailable]) { + [[self localPlayerData] loadPhotoForSize:GKPhotoSizeNormal withCompletionHandler:^(UIImage *photo, NSError *error) { + handler(photo); + if (error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; + } else { + NSError *error = [NSError errorWithDomain:[NSString stringWithFormat:@"GameCenter Unavailable"] code:GCMErrorNotAvailable userInfo:nil]; + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + } +} +#else +- (void)localPlayerPhoto:(void (^)(NSImage *playerPhoto))handler { + if ([self isGameCenterAvailable]) { + [[self localPlayerData] loadPhotoForSize:GKPhotoSizeNormal withCompletionHandler:^(NSImage *photo, NSError *error) { + handler(photo); + if (error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + }); + } + }]; + } else { + NSError *error = [NSError errorWithDomain:[NSString stringWithFormat:@"GameCenter Unavailable"] code:GCMErrorNotAvailable userInfo:nil]; + if ([[self delegate] respondsToSelector:@selector(gameCenterManager:error:)]) + [[self delegate] gameCenterManager:self error:error]; + } +} +#endif + +@end diff --git a/GC Manager/NSDataAES256.h b/GC Manager/NSDataAES256.h new file mode 100755 index 0000000..539abaf --- /dev/null +++ b/GC Manager/NSDataAES256.h @@ -0,0 +1,12 @@ +// +// NSDataAES256.h +// + +#import + +@interface NSData (AES256) + +- (NSData*) encryptedWithKey:(NSData*) key; +- (NSData*) decryptedWithKey:(NSData*) key; + +@end diff --git a/GC Manager/NSDataAES256.m b/GC Manager/NSDataAES256.m new file mode 100755 index 0000000..03a97ce --- /dev/null +++ b/GC Manager/NSDataAES256.m @@ -0,0 +1,70 @@ +// +// NSDataAES256.m +// + +#import "NSDataAES256.h" +#import + +// Key size is 32 bytes for AES256 +#define kKeySize kCCKeySizeAES256 + +@implementation NSData (AES256) + +- (NSData *)makeCryptedVersionWithKeyData:(const void *) keyData ofLength:(int) keyLength decrypt:(bool) decrypt { + // Copy the key data, padding with zeroes if needed + char key[kKeySize]; + bzero(key, sizeof(key)); + memcpy(key, keyData, keyLength > kKeySize ? kKeySize : keyLength); + + size_t bufferSize = [self length] + kCCBlockSizeAES128; + void *buffer = malloc(bufferSize); + + size_t dataUsed; + + CCCryptorStatus status = CCCrypt(decrypt ? kCCDecrypt : kCCEncrypt, + kCCAlgorithmAES128, + kCCOptionPKCS7Padding | kCCOptionECBMode, + key, kKeySize, + NULL, + [self bytes], [self length], + buffer, bufferSize, + &dataUsed); + + switch(status) { + case kCCSuccess: + return [NSData dataWithBytesNoCopy:buffer length:dataUsed]; + case kCCParamError: + NSLog(@"Error: NSDataAES256: Could not %s data: Param error", decrypt ? "decrypt" : "encrypt"); + break; + case kCCBufferTooSmall: + NSLog(@"Error: NSDataAES256: Could not %s data: Buffer too small", decrypt ? "decrypt" : "encrypt"); + break; + case kCCMemoryFailure: + NSLog(@"Error: NSDataAES256: Could not %s data: Memory failure", decrypt ? "decrypt" : "encrypt"); + break; + case kCCAlignmentError: + NSLog(@"Error: NSDataAES256: Could not %s data: Alignment error", decrypt ? "decrypt" : "encrypt"); + break; + case kCCDecodeError: + NSLog(@"Error: NSDataAES256: Could not %s data: Decode error", decrypt ? "decrypt" : "encrypt"); + break; + case kCCUnimplemented: + NSLog(@"Error: NSDataAES256: Could not %s data: Unimplemented", decrypt ? "decrypt" : "encrypt"); + break; + default: + NSLog(@"Error: NSDataAES256: Could not %s data: Unknown error", decrypt ? "decrypt" : "encrypt"); + } + + free(buffer); + return nil; +} + +- (NSData *)encryptedWithKey:(NSData *)key { + return [self makeCryptedVersionWithKeyData:[key bytes] ofLength:(int)[key length] decrypt:NO]; +} + +- (NSData *)decryptedWithKey:(NSData *)key { + return [self makeCryptedVersionWithKeyData:[key bytes] ofLength:(int)[key length] decrypt:YES]; +} + +@end diff --git a/GC Manager/Reachability.h b/GC Manager/Reachability.h new file mode 100755 index 0000000..9febed8 --- /dev/null +++ b/GC Manager/Reachability.h @@ -0,0 +1,102 @@ +/* + Copyright (c) 2011, Tony Million. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import + +#import +#import +#import +#import +#import +#import + +/** + * Does ARC support support GCD objects? + * It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ + * + * @see http://opensource.apple.com/source/libdispatch/libdispatch-228.18/os/object.h + **/ +#if OS_OBJECT_USE_OBJC +#define NEEDS_DISPATCH_RETAIN_RELEASE 0 +#else +#define NEEDS_DISPATCH_RETAIN_RELEASE 1 +#endif + + +extern NSString *const kReachabilityChangedNotification; + +typedef enum +{ + // Apple NetworkStatus Compatible Names. + NotReachable = 0, + ReachableViaWiFi = 2, + ReachableViaWWAN = 1 +} NetworkStatus; + +@class Reachability; + +typedef void (^NetworkReachable)(Reachability * reachability); +typedef void (^NetworkUnreachable)(Reachability * reachability); + +@interface Reachability : NSObject + +@property (nonatomic, copy) NetworkReachable reachableBlock; +@property (nonatomic, copy) NetworkUnreachable unreachableBlock; + + +@property (nonatomic, assign) BOOL reachableOnWWAN; + ++(Reachability*)reachabilityWithHostname:(NSString*)hostname; ++(Reachability*)reachabilityForInternetConnection; ++(Reachability*)reachabilityWithAddress:(const struct sockaddr_in*)hostAddress; ++(Reachability*)reachabilityForLocalWiFi; + +-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; + +-(BOOL)startNotifier; +-(void)stopNotifier; + +-(BOOL)isReachable; +-(BOOL)isReachableViaWWAN; +-(BOOL)isReachableViaWiFi; + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +-(BOOL)isConnectionRequired; // Identical DDG variant. +-(BOOL)connectionRequired; // Apple's routine. +// Dynamic, on demand connection? +-(BOOL)isConnectionOnDemand; +// Is user intervention required? +-(BOOL)isInterventionRequired; + +-(NetworkStatus)currentReachabilityStatus; +-(SCNetworkReachabilityFlags)reachabilityFlags; +-(NSString*)currentReachabilityString; +-(NSString*)currentReachabilityFlags; + +@end diff --git a/GC Manager/Reachability.m b/GC Manager/Reachability.m new file mode 100755 index 0000000..348b33e --- /dev/null +++ b/GC Manager/Reachability.m @@ -0,0 +1,519 @@ +/* + Copyright (c) 2011, Tony Million. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import "Reachability.h" + + +NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification"; + +@interface Reachability () + +@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; + + +#if NEEDS_DISPATCH_RETAIN_RELEASE +@property (nonatomic, assign) dispatch_queue_t reachabilitySerialQueue; +#else +@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue; +#endif + + +@property (nonatomic, strong) id reachabilityObject; + +-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; +-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; + +@end + +static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) +{ + return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c", +#if TARGET_OS_IPHONE + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', +#else + 'X', +#endif + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; +} + +//Start listening for reachability notifications on the current run loop +static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target) +#if __has_feature(objc_arc) + Reachability *reachability = ((__bridge Reachability*)info); +#else + Reachability *reachability = ((Reachability*)info); +#endif + + // we probably dont need an autoreleasepool here as GCD docs state each queue has its own autorelease pool + // but what the heck eh? + @autoreleasepool + { + [reachability reachabilityChanged:flags]; + } +} + + +@implementation Reachability + +@synthesize reachabilityRef; +@synthesize reachabilitySerialQueue; + +@synthesize reachableOnWWAN; + +@synthesize reachableBlock; +@synthesize unreachableBlock; + +@synthesize reachabilityObject; + +#pragma mark - class constructor methods ++(Reachability*)reachabilityWithHostname:(NSString*)hostname +{ + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]); + if (ref) + { + id reachability = [[self alloc] initWithReachabilityRef:ref]; + +#if __has_feature(objc_arc) + return reachability; +#else + return [reachability autorelease]; +#endif + + } + + return nil; +} + ++(Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress +{ + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + if (ref) + { + id reachability = [[self alloc] initWithReachabilityRef:ref]; + +#if __has_feature(objc_arc) + return reachability; +#else + return [reachability autorelease]; +#endif + } + + return nil; +} + ++(Reachability *)reachabilityForInternetConnection +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + return [self reachabilityWithAddress:&zeroAddress]; +} + ++(Reachability*)reachabilityForLocalWiFi +{ + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + return [self reachabilityWithAddress:&localWifiAddress]; +} + + +// initialization methods + +-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref +{ + self = [super init]; + if (self != nil) + { + self.reachableOnWWAN = YES; + self.reachabilityRef = ref; + } + + return self; +} + +-(void)dealloc +{ + [self stopNotifier]; + + if(self.reachabilityRef) + { + CFRelease(self.reachabilityRef); + self.reachabilityRef = nil; + } + + +#if !(__has_feature(objc_arc)) + [super dealloc]; +#endif + + +} + +#pragma mark - notifier methods + +// Notifier +// NOTE: this uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD +// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS. +// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want) + +-(BOOL)startNotifier +{ + SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL }; + + // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves + // woah + self.reachabilityObject = self; + + + + // first we need to create a serial queue + // we allocate this once for the lifetime of the notifier + self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); + if(!self.reachabilitySerialQueue) + { + return NO; + } + +#if __has_feature(objc_arc) + context.info = (__bridge void *)self; +#else + context.info = (void *)self; +#endif + + if (!SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)) + { +#ifdef DEBUG + NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError())); +#endif + + //clear out the dispatch queue + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; + + return NO; + } + + // set it as our reachability queue which will retain the queue + if(!SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue)) + { +#ifdef DEBUG + NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError())); +#endif + + //UH OH - FAILURE! + + // first stop any callbacks! + SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); + + // then clear out the dispatch queue + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; + + return NO; + } + + return YES; +} + +-(void)stopNotifier +{ + // first stop any callbacks! + SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); + + // unregister target from the GCD serial dispatch queue + SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); + + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; +} + +#pragma mark - reachability tests + +// this is for the case where you flick the airplane mode +// you end up getting something like this: +//Reachability: WR ct----- +//Reachability: -- ------- +//Reachability: WR ct----- +//Reachability: -- ------- +// we treat this as 4 UNREACHABLE triggers - really apple should do better than this + +#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) + +-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags +{ + BOOL connectionUP = YES; + + if(!(flags & kSCNetworkReachabilityFlagsReachable)) + connectionUP = NO; + + if( (flags & testcase) == testcase ) + connectionUP = NO; + +#if TARGET_OS_IPHONE + if(flags & kSCNetworkReachabilityFlagsIsWWAN) + { + // we're on 3G + if(!self.reachableOnWWAN) + { + // we dont want to connect when on 3G + connectionUP = NO; + } + } +#endif + + return connectionUP; +} + +-(BOOL)isReachable +{ + SCNetworkReachabilityFlags flags; + + if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + return NO; + + return [self isReachableWithFlags:flags]; +} + +-(BOOL)isReachableViaWWAN +{ +#if TARGET_OS_IPHONE + + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + // check we're REACHABLE + if(flags & kSCNetworkReachabilityFlagsReachable) + { + // now, check we're on WWAN + if(flags & kSCNetworkReachabilityFlagsIsWWAN) + { + return YES; + } + } + } +#endif + + return NO; +} + +-(BOOL)isReachableViaWiFi +{ + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + // check we're reachable + if((flags & kSCNetworkReachabilityFlagsReachable)) + { +#if TARGET_OS_IPHONE + // check we're NOT on WWAN + if((flags & kSCNetworkReachabilityFlagsIsWWAN)) + { + return NO; + } +#endif + return YES; + } + } + + return NO; +} + + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +-(BOOL)isConnectionRequired +{ + return [self connectionRequired]; +} + +-(BOOL)connectionRequired +{ + SCNetworkReachabilityFlags flags; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + + return NO; +} + +// Dynamic, on demand connection? +-(BOOL)isConnectionOnDemand +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand))); + } + + return NO; +} + +// Is user intervention required? +-(BOOL)isInterventionRequired +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & kSCNetworkReachabilityFlagsInterventionRequired)); + } + + return NO; +} + + +#pragma mark - reachability status stuff + +-(NetworkStatus)currentReachabilityStatus +{ + if([self isReachable]) + { + if([self isReachableViaWiFi]) + return ReachableViaWiFi; + +#if TARGET_OS_IPHONE + return ReachableViaWWAN; +#endif + } + + return NotReachable; +} + +-(SCNetworkReachabilityFlags)reachabilityFlags +{ + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return flags; + } + + return 0; +} + +-(NSString*)currentReachabilityString +{ + NetworkStatus temp = [self currentReachabilityStatus]; + + if(temp == reachableOnWWAN) + { + // updated for the fact we have CDMA phones now! + return NSLocalizedString(@"Cellular", @""); + } + if (temp == ReachableViaWiFi) + { + return NSLocalizedString(@"WiFi", @""); + } + + return NSLocalizedString(@"No Connection", @""); +} + +-(NSString*)currentReachabilityFlags +{ + return reachabilityFlags([self reachabilityFlags]); +} + +#pragma mark - callback function calls this method + +-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags +{ + if([self isReachableWithFlags:flags]) + { + if(self.reachableBlock) + { + self.reachableBlock(self); + } + } + else + { + if(self.unreachableBlock) + { + self.unreachableBlock(self); + } + } + + // this makes sure the change notification happens on the MAIN THREAD + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification + object:self]; + }); +} + +#pragma mark - Debug Description + +- (NSString *) description; +{ + NSString *description = [NSString stringWithFormat:@"<%@: %#x>", + NSStringFromClass([self class]), (unsigned int) self]; + return description; +} + +@end diff --git a/GameCenterManager.xcodeproj/project.xcworkspace/xcuserdata/Spencers.xcuserdatad/UserInterfaceState.xcuserstate b/GameCenterManager.xcodeproj/project.xcworkspace/xcuserdata/Spencers.xcuserdatad/UserInterfaceState.xcuserstate index f594e62..0317a32 100755 Binary files a/GameCenterManager.xcodeproj/project.xcworkspace/xcuserdata/Spencers.xcuserdatad/UserInterfaceState.xcuserstate and b/GameCenterManager.xcodeproj/project.xcworkspace/xcuserdata/Spencers.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Images.xcassets/LaunchImage.launchimage/Default@2x-1.png b/Images.xcassets/LaunchImage.launchimage/Default@2x-1.png new file mode 100644 index 0000000..679ac16 Binary files /dev/null and b/Images.xcassets/LaunchImage.launchimage/Default@2x-1.png differ diff --git a/README.md b/README.md index a8591af..c9e2f9c 100755 --- a/README.md +++ b/README.md @@ -45,10 +45,10 @@ GameCenter Manager automatically checks if Game Center is available before perfo BOOL isAvailable = [[GameCenterManager sharedManager] checkGameCenterAvailability]; This method will perform the following checks in the following order: - 1. Current OS version new enough to run Game Center. iOS 4.1 or OS X 10.8. Some Game Center methods require newer OS versions which will be checked (ex. challenges and some multiplayer features). - 2. GameKit API availability. The `GKLocalPlayer` class must be available at a minimum. - 3. Internet Connection. The `Reachability` class is used to determine if there is an active internet connection. GameCenterManager will still work without internet, however all saved data can only be uploaded with an internet connection. - 4. Local Player. Check to make sure a local player is logged in and authenticated. + 1. Current OS version new enough to run Game Center. iOS 4.1 or OS X 10.8. Some Game Center methods require newer OS versions which will be checked (ex. challenges and some multiplayer features). + 2. GameKit API availability. The `GKLocalPlayer` class must be available at a minimum. + 3. Internet Connection. The `Reachability` class is used to determine if there is an active internet connection. GameCenterManager will still work without internet, however all saved data can only be uploaded with an internet connection. + 4. Local Player. Check to make sure a local player is logged in and authenticated. 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.