This commit is contained in:
watrabi
2025-09-18 17:55:52 -04:00
commit 977f1ff4b8
15030 changed files with 17324420 additions and 0 deletions
@@ -0,0 +1,15 @@
//
// UIScreen+PortraitSize.h
// RobloxMobile
//
// Created by Ariel Lichtin on 9/9/14.
// Copyright (c) 2014 ROBLOX. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIScreen (PortraitBounds)
-(CGRect) portraitBounds;
@end
@@ -0,0 +1,27 @@
//
// UIScreen+PortraitSize.m
// RobloxMobile
//
// Created by Ariel Lichtin on 9/9/14.
// Copyright (c) 2014 ROBLOX. All rights reserved.
//
#import "UIScreen+PortraitBounds.h"
@implementation UIScreen (PortraitBounds)
- (CGRect) portraitBounds
{
// In iOS8, Apple fixed "bounds": It now returns the screen size based on the current orientation.
// portraitBounds will return the bounds always in the fixed coordinate space
if( [self respondsToSelector:@selector(fixedCoordinateSpace)] )
{
return [self.coordinateSpace convertRect:self.bounds toCoordinateSpace:self.fixedCoordinateSpace];
}
else
{
return self.bounds;
}
}
@end
+32
View File
@@ -0,0 +1,32 @@
//
// CrashReporter.h
// RobloxMobile
//
// Created by Ganesh Agrawal on 6/25/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "util/standardout.h"
#import "rbx/signal.h"
@interface CrashReporter : NSObject
{
rbx::signals::scoped_connection messageOutConnection;
}
- (NSString*) activeCrashReporterString;
- (void) tryLogMessage:(const RBX::StandardOutMessage&) message;
- (void) logStringKeyValue:(NSString*) key withValue:(NSString*) value;
- (void) logBoolKeyValue:(NSString*) key withValue:(BOOL) value;
- (void) logIntKeyValue:(NSString*) key withValue:(int) value;
- (void) logFloatKeyValue:(NSString*) key withValue:(float) value;
+(CrashReporter*) sharedInstance;
@end
+218
View File
@@ -0,0 +1,218 @@
//
// CrashReporter.m
// RobloxMobile
//
// Created by Ganesh Agrawal on 6/25/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import "CrashReporter.h"
#import "RobloxCachedFlags.h"
#import "RobloxWebUtility.h"
#import "util/standardout.h"
#import "ObjectiveCUtilities.h"
#include "FastLog.h"
#import "RobloxGoogleAnalytics.h"
#import <Crashlytics/Crashlytics.h>
// DO NOT RELEASE WITH TESTING ENABLED FOR APP STORE SUBMIT
//#define ROBLOX_TESTING 1
@implementation CrashReporter
bool crashlyticsActive = false;
#define CHANNEL_OUTPUT 1
#define CHANNEL_LOGGING 2
static void fastLogMesage(FLog::Channel id, const char* message)
{
if(id == CHANNEL_OUTPUT)
{
printf("FLog%02d: %s\n", id, message);
}
else if (id == CHANNEL_LOGGING)
{
if(crashlyticsActive)
{
CLS_LOG("%@", [NSString stringWithUTF8String:message]);
}
// Always do the NSLog
NSLog(@"%s", message);
}
}
-(NSString*) activeCrashReporterString
{
if(crashlyticsActive)
return @"Crashlytics";
else
return @"None";
}
+ (CrashReporter*)sharedInstance
{
static dispatch_once_t rbxCrashReporterFlagsPred = 0;
__strong static CrashReporter* _sharedObject = nil;
dispatch_once(&rbxCrashReporterFlagsPred, ^{ // Need to use GCD for thread-safe allocation
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-(void) setupCrashlytics
{
#ifndef _DEBUG
if(!crashlyticsActive)
{
crashlyticsActive = true;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0), ^{
id crashlyticsKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CrashlyticsKey"];
if (!crashlyticsKey)
{
NSLog(@"Crash Reporter cannot initialize Crashlytics due to missing API key");
return;
}
if(![crashlyticsKey isKindOfClass:[NSString class]])
{
NSLog(@"Crash Reporter not initializing Crashlytics because plist entry is not a string");
return;
}
NSString* crashlyticsString = (NSString*) crashlyticsKey;
if([crashlyticsString length] <= 0)
{
NSLog(@"Crash Reporter cannot initialize Crashlytics due to API key having length 0");
return;
}
[Crashlytics startWithAPIKey:crashlyticsString];
NSLog(@"Crash Reporter initialized: Crashlytics");
[[NSUserDefaults standardUserDefaults] setObject:@"Crashlytics" forKey:@"CrashReporterSDK"];
[[NSUserDefaults standardUserDefaults] synchronize];
});
}
#endif
}
- (void) setupFastLogConnection
{
FLog::SetExternalLogFunc(fastLogMesage);
messageOutConnection = RBX::StandardOut::singleton()->messageOut.connect(boostFuncFromSelector_1< const RBX::StandardOutMessage& >
(@selector(tryLogMessage:),self) );
}
- (void) setup
{
[[RobloxCachedFlags sharedInstance] sync];
NSString *gameStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxGameState"];
NSString *appStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxAppState"];
// clear out if we cleanly exited
if ([appStateString isEqualToString:@"inBackground"] || [appStateString isEqualToString:@"terminated"] )
{
appStateString = nil;
}
if (gameStateString || appStateString)
{
NSString *crashReporterSDK = [[NSUserDefaults standardUserDefaults] stringForKey:@"CrashReporterSDK"];
if (crashReporterSDK)
{
if([crashReporterSDK rangeOfString:@"Crashlytics"].location != NSNotFound)
[self setupCrashlytics];
[self setupFastLogConnection];
return;
}
}
srand (time(NULL));
int rnd = (rand() % 100) + 1;
NSInteger pctCrashlytics = 0;
BOOL bCrashlytics = [[RobloxCachedFlags sharedInstance] getInt:@"CrashlyticsPercentage" withValue:&pctCrashlytics];
if (bCrashlytics && pctCrashlytics > 0)
{
if (rnd <= pctCrashlytics)
{
[self setupCrashlytics];
}
}
[self setupFastLogConnection];
}
-(id) init
{
if(self = [super init])
{
[self setup];
}
return self;
}
- (void) tryLogMessage:(const RBX::StandardOutMessage&) message
{
NSString* logString = [NSString stringWithCString:message.message.c_str() encoding:NSUTF8StringEncoding];
if([logString isEqualToString:@""])
return;
RBX::MessageType messageType = message.type;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
int tfLoggingLevel = iosSettings->GetValueCrashLoggingLevel();
if ( messageType >= tfLoggingLevel )
{
if (crashlyticsActive)
{
CLS_LOG("%@", logString);
}
}
// Always do the NSLog
NSLog(@"%@", logString);
});
}
- (void) logStringKeyValue:(NSString*) key withValue:(NSString*) value
{
if (crashlyticsActive)
[[Crashlytics sharedInstance] setObjectValue:value forKey:key];
}
- (void) logBoolKeyValue:(NSString*) key withValue:(BOOL) value
{
if (crashlyticsActive)
[[Crashlytics sharedInstance]setIntValue:value forKey:key];
}
- (void) logIntKeyValue:(NSString*) key withValue:(int) value
{
if (crashlyticsActive)
[[Crashlytics sharedInstance]setIntValue:value forKey:key];
}
- (void) logFloatKeyValue:(NSString*) key withValue:(float) value
{
if (crashlyticsActive)
[[Crashlytics sharedInstance]setIntValue:value forKey:key];
}
@end
+144
View File
@@ -0,0 +1,144 @@
// This file is used for two targets:
// 1. Used in Mac Roblox Player app, this is the place where actual file resides
// 2. Used in Qt version of Roblox Studio as a soft link from above
#include "FunctionMarshaller.h"
#undef min
#undef max
#include "util/StandardOut.h"
#include "rbx/boost.hpp"
#include "Roblox.h"
using namespace RBX;
FunctionMarshaller::FunctionMarshaller(DWORD threadID)
:refCount(0)
{
this->threadID = threadID;
}
FunctionMarshaller::~FunctionMarshaller()
{
boost::function<void()>* f;
while (asyncCalls.pop_if_present(f))
delete f;
RBXASSERT(threadID == GetCurrentThreadId());
#ifdef _DEBUG
{
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
RBXASSERT (refCount==0);
// Nobody is using this window
RBXASSERT (staticData().windows.find(threadID) == staticData().windows.end());
}
#endif
}
FunctionMarshaller* FunctionMarshaller::GetWindow()
{
// Share a common FunctionMarshaller in a given Thread
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
DWORD threadID = GetCurrentThreadId();
std::map<DWORD, FunctionMarshaller*>::iterator find = staticData().windows.find(threadID);
if (find != staticData().windows.end())
{
// We already created a window, so use it again
find->second->refCount++;
return find->second;
}
else
{
// Create a new window
FunctionMarshaller* window = new FunctionMarshaller(threadID);
staticData().windows[threadID] = window;
window->refCount++;
return window;
}
}
void FunctionMarshaller::ReleaseWindow(FunctionMarshaller* window)
{
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
window->refCount--;
if (window->refCount==0)
{
// Nobody is using this window
staticData().windows.erase(window->threadID);
}
}
void FunctionMarshaller::handleAppEvent(void *pClosure)
{
FunctionMarshaller::Closure* closure = (FunctionMarshaller::Closure*)pClosure;
RBX::CEvent *pWaitEvent = closure->waitEvent;
try
{
boost::function<void()>* pF = closure->f;
(*pF)();
delete pF;
delete closure;
}
catch (RBX::base_exception& e)
{
StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e);
closure->errorMessage = e.what();
}
// If a task is waiting on an event, set it
if (pWaitEvent)
{
pWaitEvent->Set();
}
}
void FunctionMarshaller::freeAppEvent(void *pClosure)
{
FunctionMarshaller::Closure* closure = (FunctionMarshaller::Closure*)pClosure;
boost::function<void()>* pF = closure->f;
delete pF;
delete closure;
}
void FunctionMarshaller::Execute(boost::function<void()> job, CEvent *waitEvent)
{
if (threadID == GetCurrentThreadId())
job();
else
{
Closure *pClosure = new Closure;
pClosure->f = new boost::function<void()>(job);
pClosure->waitEvent = waitEvent;
Roblox::sendAppEvent(pClosure);
}
}
void FunctionMarshaller::Submit(boost::function<void()> job)
{
Closure *pClosure = new Closure;
pClosure->f = new boost::function<void()>(job);
pClosure->waitEvent = NULL;
Roblox::postAppEvent(pClosure);
}
void FunctionMarshaller::ProcessMessages()
{
Roblox::processAppEvents();
}
FunctionMarshaller::StaticData::~StaticData()
{
}
+69
View File
@@ -0,0 +1,69 @@
// This file is used for two targets:
// 1. Used in Mac Roblox Player app, this is the place where actual file resides
// 2. Used in Qt version of Roblox Studio as a soft link from above
#pragma once
#ifdef _WIN32
// This code is platform-specific
#error
#endif
#include <map>
#include "rbx/threadsafe.h"
#include "rbx/CEvent.h"
namespace RBX {
// A very handy class for marshalling a function across Windows threads (sync and async)
class FunctionMarshaller
{
public:
private:
struct StaticData
{
std::map<DWORD, FunctionMarshaller*> windows;
boost::recursive_mutex windowsCriticalSection; // TODO: Would non-recursive be safe here?
~StaticData();
};
SAFE_STATIC(StaticData, staticData)
rbx::safe_queue<boost::function<void()>*> asyncCalls;
int refCount;
DWORD threadID;
FunctionMarshaller(DWORD threadID);
~FunctionMarshaller();
public:
// TODO: Wrap with a reference counter and then remove ~StaticData() cleanup code and remove ReleaseWindow()
static FunctionMarshaller* GetWindow();
static void ReleaseWindow(FunctionMarshaller* window);
static void handleAppEvent(void *pClosure);
static void freeAppEvent(void *pClosure);
struct Closure
{
boost::function<void()>* f;
std::string errorMessage;
RBX::CEvent *waitEvent;
};
void Execute(boost::function<void()> job, CEvent *waitEvent);
void Submit(boost::function<void()> job);
// Call this only from the Window's thread
void ProcessMessages();
// virtual void OnFinalMessage(HWND hWnd);
private:
// LRESULT OnEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
// LRESULT OnAsyncEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
};
}
+25
View File
@@ -0,0 +1,25 @@
//
// GameKitHelper.h
// RobloxMobile
//
// Created by Ganesh on 5/2/14.
// Copyright (c) 2014 ROBLOX. All rights reserved.
//
// Include the GameKit framework
#import <GameKit/GameKit.h>
@interface GameKitHelper : NSObject
{
NSString* gameCenterActiveNotification;
NSString* gameCenterDisabledNotification;
}
+ (id) sharedInstance;
-(void) authenticateLocalPlayer;
-(NSString*) getGameCenterActiveNotification;
-(NSString*) getGameCenterDisabledNotification;
@end
+304
View File
@@ -0,0 +1,304 @@
//
// GameKitHelper.m
// RobloxMobile
//
// Created by Ganesh on 5/2/14.
// Copyright (c) 2014 ROBLOX. All rights reserved.
//
#import "GameKitHelper.h"
#import "OnlineGameViewController.h"
#import "RobloxInfo.h"
#import "LoginManager.h"
@interface NSString (URLEncoding)
-(NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding;
@end
@implementation NSString (URLEncoding)
-(NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding {
return (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)self,
NULL,
(CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ",
CFStringConvertNSStringEncodingToEncoding(encoding));
}
@end
@implementation GameKitHelper
+(id) sharedInstance {
static dispatch_once_t onceToken = 0;
__strong static id _sharedObject = nil;
dispatch_once(&onceToken, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-(id) init
{
if(self = [super init])
{
gameCenterActiveNotification = [[NSString alloc] initWithString:@"RBXGameCenterActiveNotifier"];
gameCenterDisabledNotification = [[NSString alloc] initWithString:@"RBXGameCenterDisabledNotifier"];
}
return self;
}
-(void) dealloc
{
[gameCenterActiveNotification release];
[gameCenterDisabledNotification release];
[super dealloc];
}
-(NSString*) getGameCenterActiveNotification
{
return gameCenterActiveNotification;
}
-(NSString*) getGameCenterDisabledNotification
{
return gameCenterDisabledNotification;
}
-(void) authenticateLocalPlayer
{
GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer];
[[NSNotificationCenter defaultCenter] postNotificationName:gameCenterActiveNotification object:self userInfo:nil];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error)
{
// Apple has auntheticated the GC User
if (localPlayer.authenticated)
{
NSLog(@"Logged in with GameCenter");
// Ask Apple to generate the signature, certificate for our server verification if he is authenticated Game Center with the player id
[localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {
// Apple server errored out
if (error)
{
[[LoginManager sharedInstance] logoutRobloxUser];
NSLog(@"Apple Game Center Server Errored out: %@", error.description);
[[NSNotificationCenter defaultCenter] postNotificationName:gameCenterDisabledNotification object:self userInfo:nil];
}
else // Apple Server cert generation successful
{
#if 0
NSLog(@"Public Key URL: %@", publicKeyUrl);
NSLog(@"Timestamp: %@", [NSString stringWithFormat:@"%llu", timestamp]);
NSLog(@"Signature: %@", [signature base64EncodedStringWithOptions:0]);
NSLog(@"Salt: %@", [salt base64EncodedStringWithOptions:0]);
NSLog(@"Player ID: %@", localPlayer.playerID);
NSLog(@"Display Name: %@", localPlayer.displayName);
NSLog(@"Alias Name: %@", localPlayer.alias);
NSLog(@"Bundle ID: %@", [[NSBundle mainBundle] bundleIdentifier]);
NSLog(@"Is UnderAge: %@", localPlayer.isUnderage ? @"Yes" : @"No" );
#endif
//https://api.sitetest3.pizzaboxer.fun/apple-game-center/authorize?playerId=G:2030020262&signature=V9GPDkwA+k3BYfiYLbauEwkTZP8yiw2YEJKFdmCukcyKmsTQZmvT4WAqbRL6QNrqLzXAn41veo9Tg6XWjv0WgXm7bPXNzTkIXjmLBUHXg0Y8cogxr8bQe+ClNev0FMgmU9eX+kcYdFy4U0124KdmRYYobq2e1RViiI4pM9oqWFg3DQ+rouAqIK15S3zXAM4ZaUt+qYFnqApp0nlOjL3vZCtrwybCrWphx6SWoWOOa3RtZ44vl/F192bFumw2/KFt6mwOgOWlVn5Fvk55gIJ0faQs96/0MFhpskkHr5e2ZYjhlXU78e63m2GplZlDcjs6s/Zo3yTVhJiKu0W3byRulg==&publicKeyUrl=https://sandbox.gc.apple.com/public-key/gc-sb.cer&salt=12NX6A==&timestamp=1400192105946&bundleid=wtf.watrbx.Age-of-Kings
// URL for sigin of Game Center user, assuming the GameCenter equivalent account is already created with Roblox
NSString* finalURL =[NSString stringWithFormat:@"%@/apple/game-center/signin?playerId=%@&signature=%@&bundleid=%@&publicKeyUrl=%@&salt=%@&timestamp=%@"
, [RobloxInfo getApiBaseUrl]
, [localPlayer.playerID urlEncodeUsingEncoding:NSUTF8StringEncoding]
, [[signature base64EncodedStringWithOptions:0] urlEncodeUsingEncoding:NSUTF8StringEncoding]
, [[[NSBundle mainBundle] bundleIdentifier] urlEncodeUsingEncoding:NSUTF8StringEncoding]
, publicKeyUrl
, [[salt base64EncodedStringWithOptions:0] urlEncodeUsingEncoding:NSUTF8StringEncoding]
, [NSString stringWithFormat:@"%llu", timestamp]
];
#if 0
NSLog(@" URL: %@", finalURL);
NSLog(@"UserAgent: %@", [RobloxInfo getUserAgentString]);
#endif
NSURL *url = [NSURL URLWithString: finalURL];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60*7];
[RobloxInfo setDefaultHTTPHeadersForRequest:theRequest];
[theRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[theRequest setHTTPMethod:@"POST"];
// Make the signin request with Roblox servers
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:theRequest queue:queue completionHandler:
^(NSURLResponse *response, NSData *receiptResponseData, NSError *error)
{
// Got the response
NSHTTPURLResponse* urlResponse = ( NSHTTPURLResponse*) response;
if ([urlResponse statusCode] == 200) // Successful 200 Status Code
{
NSDictionary* dict = [[NSDictionary alloc] init];
NSError* error = nil;
dict = [NSJSONSerialization JSONObjectWithData:receiptResponseData options:kNilOptions error:&error];
if (!error) // No Error processsing json data from our servers
{
NSNumber * n = [dict valueForKey:@"success"];
BOOL success = [n boolValue];
if (success) // GC User exists on Roblox Server, do the Login
{
#if 0
NSArray * all = [NSHTTPCookie cookiesWithResponseHeaderFields:[urlResponse allHeaderFields] forURL:[NSURL URLWithString:[RobloxInfo getBaseUrl]]];
NSLog(@"\n\n\n****Cookies Recd Start****\n");
for (NSHTTPCookie *each in all)
{
NSLog(@"Name: %@ : Value: %@ Date: %@ Path: %@ Domain: %@ HTTP Only: %@\n\n", each.name, each.value, each.expiresDate, each.path, each.domain, (each.isHTTPOnly ? @"True" : @"False"));
}
NSLog(@"\n****Cookies Recd End****\n\n\n");
#endif
[[LoginManager sharedInstance] doGameCenterLogin];
} // GC User exists on Roblox Server, do the Login
else // GC User do not exist exists on Roblox Server, do a sign up on Roblox servers by creating account
{
// Try doing a signup now
NSString* finalURL =[NSString stringWithFormat:@"%@/apple/game-center/signup?playerId=%@&isUnderAge=%@&signature=%@&bundleid=%@&publicKeyUrl=%@&salt=%@&timestamp=%@"
, [RobloxInfo getApiBaseUrl]
, [localPlayer.playerID urlEncodeUsingEncoding:NSUTF8StringEncoding]
, localPlayer.isUnderage ? @"true" : @"false"
, [[signature base64EncodedStringWithOptions:0] urlEncodeUsingEncoding:NSUTF8StringEncoding]
, [[[NSBundle mainBundle] bundleIdentifier] urlEncodeUsingEncoding:NSUTF8StringEncoding]
, publicKeyUrl
, [[salt base64EncodedStringWithOptions:0] urlEncodeUsingEncoding:NSUTF8StringEncoding]
, [NSString stringWithFormat:@"%llu", timestamp]
];
#if 0
NSLog(@" URL: %@", finalURL);
NSLog(@"UserAgent: %@", [RobloxInfo getUserAgentString]);
#endif
NSURL *url = [NSURL URLWithString: finalURL];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60*7];
[RobloxInfo setDefaultHTTPHeadersForRequest:theRequest];
[theRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[theRequest setHTTPMethod:@"POST"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:theRequest queue:queue completionHandler:
^(NSURLResponse *response, NSData *receiptResponseData, NSError *error)
{ // Got response
NSHTTPURLResponse* urlResponse = ( NSHTTPURLResponse*) response;
if ([urlResponse statusCode] == 200) // successful response
{
NSDictionary* dict = [[NSDictionary alloc] init];
NSError* error = nil;
dict = [NSJSONSerialization JSONObjectWithData:receiptResponseData options:kNilOptions error:&error];
if (!error) // No Error processsing json data from our servers
{
NSNumber * n = [dict valueForKey:@"success"];
BOOL success = [n boolValue];
if (success) // successful sign up, do a login
{
#if 0
NSArray * all = [NSHTTPCookie cookiesWithResponseHeaderFields:[urlResponse allHeaderFields] forURL:[NSURL URLWithString:[RobloxInfo getBaseUrl]]];
NSLog(@"\n\n\n****Cookies Recd Start****\n");
for (NSHTTPCookie *each in all)
{
NSLog(@"Name: %@ : Value: %@ Date: %@ Path: %@ Domain: %@ HTTP Only: %@\n\n", each.name, each.value, each.expiresDate, each.path, each.domain, (each.isHTTPOnly ? @"True" : @"False"));
}
NSLog(@"\n****Cookies Recd End****\n\n\n");
#endif
[[LoginManager sharedInstance] doGameCenterLogin];
} // successful sign up, do a login
else // unsuccessful with signup
{
[[NSNotificationCenter defaultCenter] postNotificationName:gameCenterDisabledNotification object:self userInfo:nil];
[[LoginManager sharedInstance] logoutRobloxUser];
} // unsuccessful with signup
}
else // Error processsing json data from our servers
{
NSLog(@"Error in GC JSON Data with Roblox Signup: %@", [error description]);
[[NSNotificationCenter defaultCenter] postNotificationName:gameCenterDisabledNotification object:self userInfo:nil];
[[LoginManager sharedInstance] logoutRobloxUser];
} // Error processsing json data from our servers
} // successful response
else // non successful response
{
NSLog(@"GC Failed Response Code %d", [urlResponse statusCode]);
[[NSNotificationCenter defaultCenter] postNotificationName:gameCenterDisabledNotification object:self userInfo:nil];
[[LoginManager sharedInstance] logoutRobloxUser];
}
}]; // Got response
} // GC User do not exist exists on Roblox Server, do a sign up on Roblox servers by creating account
} // No Error processsing json data from our servers
else // Error processsing json data from our servers
{
NSLog(@"Error in GC JSON Data from Roblox Servers: %@", [error description]);
[[NSNotificationCenter defaultCenter] postNotificationName:gameCenterDisabledNotification object:self userInfo:nil];
[[LoginManager sharedInstance] logoutRobloxUser];
} // Error processsing json data from our servers
} // Successful 200 Status Code
else
{
NSLog(@"GC Failed Response Code %d", [urlResponse statusCode]);
[[NSNotificationCenter defaultCenter] postNotificationName:gameCenterDisabledNotification object:self userInfo:nil];
[[LoginManager sharedInstance] logoutRobloxUser];
}
}]; // Make the signin request with Roblox servers
} // Apple Server cert generation successful
}]; // Ask Apple to generate the signature, certificate for our server verification if he is authenticated Game Center with the player id
} // Apple has auntheticated the GC User
else if(viewController) // Apple is going to ask the user to enter the user name and passord for GC
{
[[LoginManager sharedInstance] logoutRobloxUser];
NSLog(@"Ask to login with Game Center");
[self presentViewController:viewController];
} else // Apple has decided not to show the GC login after user has dismissed three times with cancel
{
NSLog(@"Disable Game Center");
[[NSNotificationCenter defaultCenter] postNotificationName:gameCenterDisabledNotification object:self userInfo:nil];
}
};
}
-(void)presentViewController:(UIViewController*)vc
{
[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:vc animated:YES completion:nil];
}
@end
+23
View File
@@ -0,0 +1,23 @@
/*
* GameVerbs.h
* iOS
*
* Created on 5/18/14.
* Copyright 2014 ROBLOX. All rights reserved.
*
*/
#pragma once
#include "v8tree/Verb.h"
using namespace RBX;
class LeaveGameVerb : public RBX::Verb
{
class RobloxView *robloxView;
public :
LeaveGameVerb(class RobloxView *pRobloxView, VerbContainer* container);
virtual void doIt(RBX::IDataState* dataState);
};
+28
View File
@@ -0,0 +1,28 @@
/*
* GameVerbs.mm
* iOS
*
* Created on 5/19/14
* Copyright 2014 ROBLOX. All rights reserved.
*
*/
#import "PlaceLauncher.h"
#include "GameVerbs.h"
#include "RobloxView.h"
#include "v8datamodel/GameBasicSettings.h"
LeaveGameVerb::LeaveGameVerb(RobloxView *pRobloxView, VerbContainer* container) :
Verb(container, "Exit")
,robloxView(pRobloxView)
{
}
void LeaveGameVerb::doIt(RBX::IDataState* dataState)
{
[[PlaceLauncher sharedInstance] leaveGame:YES];
RBX::GlobalBasicSettings::singleton()->saveState();
}
@@ -0,0 +1,29 @@
//
// ControlComponent.h
// IOSClient
//
// Created by Ben Tkacheff on 9/6/12.
// Copyright (c) 2012 tolkring@gmail.com. All rights reserved.
//
#pragma once
#import <UIKit/UIKit.h>
#include "v8datamodel/UserInputService.h"
#include "v8datamodel/GamepadService.h"
#include "v8datamodel/TouchInputService.h"
@class ControlView;
@interface ControlComponent : UIImageView
{
}
- (RBX::Game*) getGameFromControlView;
- (RBX::GamepadService*) getGamepadServiceForGameDataModel;
- (RBX::UserInputService*) getUserInputServiceForGameDataModel;
- (RBX::TouchInputService*) getTouchInputServiceForGameDataModel;
- (ControlView*) findControlView;
@end
@@ -0,0 +1,84 @@
//
// ControlComponent.m
// IOSClient
//
// Created by Ben Tkacheff on 9/6/12.
// Copyright (c) 2012 tolkring@gmail.com. All rights reserved.
//
#import "ControlComponent.h"
#import "ControlView.h"
#include "v8datamodel/TouchInputService.h"
@implementation ControlComponent
- (id) init
{
if (self = [super init])
{
self.userInteractionEnabled = true;
}
return self;
}
- (ControlView*) findControlView
{
if ([self isKindOfClass:[ControlView class]])
return (ControlView*)self;
id nextSuperView = self.superview;
while (nextSuperView != nil)
{
if ([nextSuperView isKindOfClass:[ControlView class]])
return nextSuperView;
if ([nextSuperView isKindOfClass:[UIView class]])
{
UIView* theUIView = nextSuperView;
nextSuperView = theUIView.superview;
}
else
nextSuperView = nil;
}
return nil;
}
-(RBX::Game*) getGameFromControlView
{
ControlView* controlView = [self findControlView];
if (controlView == nil)
return nil;
return [controlView getGame].get();
}
- (RBX::GamepadService*) getGamepadServiceForGameDataModel
{
if (RBX::Game* game = [self getGameFromControlView])
if (shared_ptr<RBX::DataModel> currDataModel = game->getDataModel())
return RBX::ServiceProvider::create<RBX::GamepadService>(currDataModel.get());
return nil;
}
- (RBX::UserInputService*) getUserInputServiceForGameDataModel
{
if (RBX::Game* game = [self getGameFromControlView])
if (shared_ptr<RBX::DataModel> currDataModel = game->getDataModel())
return RBX::ServiceProvider::find<RBX::UserInputService>(currDataModel.get());
return nil;
}
- (RBX::TouchInputService*) getTouchInputServiceForGameDataModel
{
if (RBX::Game* game = [self getGameFromControlView])
if (shared_ptr<RBX::DataModel> currDataModel = game->getDataModel())
return RBX::ServiceProvider::create<RBX::TouchInputService>(currDataModel.get());
return nil;
}
@end
@@ -0,0 +1,65 @@
//
// ControlView.h
// IOSClient
//
// Created by Ben Tkacheff on 8/21/12.
// Copyright (c) 2012 tolkring@gmail.com. All rights reserved.
//
#pragma once
#include "v8datamodel/UserInputService.h"
#include "V8DataModel/TextBox.h"
#include "v8datamodel/Game.h"
#import <UIKit/UIKit.h>
#import "RbxInputView.h"
@interface ControlView : ControlComponent <UIGestureRecognizerDelegate>
{
RbxInputView* rbxInputView;
RBX::Vector2int16 frameSize;
UITouch* tapTouch;
G3D::Vector2 tapTouchBeginPos;
double tapSensitivity;
int tapTouchMoveTolerance;
boost::weak_ptr<RBX::Game> game;
rbx::signals::scoped_connection dmUserInputTextBoxFocusCon;
rbx::signals::scoped_connection dmUserInputTextBoxReleaseFocusCon;
rbx::signals::scoped_connection dmUserInputProcessMouseEventCon;
// fake mouse events (for backwards compatibility
shared_ptr<RBX::InputObject> mouseButton1Event;
shared_ptr<RBX::InputObject> mouseMoveEvent;
}
- (id) init:(CGRect)frame withGame:(boost::shared_ptr<RBX::Game>) newGame;
- (void) dealloc;
- (RbxInputView*) getRbxInputView;
- (void) disconnectEvents;
- (void) setupEvents;
- (void) setGame:(boost::shared_ptr<RBX::Game>) newGame;
- (boost::shared_ptr<RBX::Game>) getGame;
- (void) textBoxFocusGained:(boost::shared_ptr<RBX::TextBox>) textBoxFocused;
- (void) textBoxFocusLost:(boost::shared_ptr<RBX::TextBox>) textBoxUnfocused;
// Gesture Recognizers
// Tap
- (void) oneFingerSingleTap;
- (UITouch*) checkTouchesForTap:(NSSet *)touches withEvent:(UIEvent *)event;
- (void) checkTapTouchMove:(NSSet*) touchesSet;
- (void) invalidateTapGesture:(id) oldTapTouch;
- (void) postMouseEventProcessed:(bool) processedEvent inputObject: (void*) uiTouch event:(const shared_ptr<RBX::InputObject>&) event;
@end
@@ -0,0 +1,312 @@
//
// ControlView.m
// IOSClient
//
// Created by Ben Tkacheff on 8/21/12.
// Copyright (c) 2012 tolkring@gmail.com. All rights reserved.
//
#import "ControlView.h"
#import "GameKeyboard.h"
#import "PlaceLauncher.h"
#include "ObjectiveCUtilities.h"
#import "RobloxNotifications.h"
@implementation ControlView
- (id) init:(CGRect)frame withGame:(boost::shared_ptr<RBX::Game>) newGame
{
self = [super init];
if (self)
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(gotStartLeaveGameNotification:)
name:RBX_NOTIFY_GAME_START_LEAVING
object:nil ];
game = newGame;
[self setupEvents];
// initial size has coordinates for height/width flipped, as it always returns portait mode, no matter our current orientation :(
CGRect correctRect = CGRectMake(0,0,frame.size.height,frame.size.width);
frameSize = RBX::Vector2int16(frame.size.height, frame.size.width);
// self initialization
self.multipleTouchEnabled = YES;
self.frame = correctRect;
[self setupInputControls];
mouseMoveEvent = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(RBX::InputObject::TYPE_MOUSEMOVEMENT,
RBX::InputObject::INPUT_STATE_CHANGE,
RBX::Vector3(-1,-1,0),
RBX::Vector3(0,0,0),
[self getGame]->getDataModel().get());
mouseButton1Event = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(RBX::InputObject::TYPE_MOUSEBUTTON1,
RBX::InputObject::INPUT_STATE_BEGIN,
RBX::Vector3(-1,-1,0),
RBX::Vector3(0,0,0),
[self getGame]->getDataModel().get());
}
return self;
}
-(void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[GameKeyboard sharedInstance] setParentView:nil];
rbxInputView = nil;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (RbxInputView*) getRbxInputView
{
return rbxInputView;
}
- (void) setGame:(boost::shared_ptr<RBX::Game>) newGame
{
game = newGame;
if(boost::shared_ptr<RBX::Game> sharedGame = game.lock())
{
[self dataModelChanged:sharedGame->getDataModel().get()];
}
}
- (void) gotStartLeaveGameNotification:(NSNotification *)aNotification
{
[self disconnectEvents];
}
-(void) dataModelChanged:(RBX::DataModel*) dataModel
{
if(dataModel)
{
[self setupEvents];
[self setupInputControls];
}
else // we have a null datamodel, tear down connections
[self disconnectEvents];
}
- (void) postMouseEventProcessed:(bool) processedEvent inputObject: (void*) uiTouch event: (const shared_ptr<RBX::InputObject>&) event
{
if (uiTouch && processedEvent)
{
void* tapTouchPtr = (__bridge void*) tapTouch;
if( uiTouch && (uiTouch == tapTouchPtr) )
[self invalidateTapGesture:nil];
}
}
- (void) textBoxFocusGained:(boost::shared_ptr<RBX::TextBox>) textBoxFocused
{
if(textBoxFocused != NULL && textBoxFocused != boost::shared_ptr<RBX::TextBox>())
[[GameKeyboard sharedInstance] showKeyboardWithTextBox: textBoxFocused];
else
[[GameKeyboard sharedInstance] showKeyboard: ""];
}
- (void) textBoxFocusLost:(boost::shared_ptr<RBX::TextBox>) textBoxUnfocused
{
dispatch_async(dispatch_get_main_queue(), ^{
[[GameKeyboard sharedInstance] hideKeyboard];
});
}
- (boost::shared_ptr<RBX::Game>) getGame
{
return game.lock();
}
- (void) setupEvents
{
if(boost::shared_ptr<RBX::Game> sharedGame = game.lock())
{
[self bindToUserInputService:sharedGame->getDataModel()];
}
}
- (void) disconnectEvents
{
dmUserInputTextBoxFocusCon.disconnect();
dmUserInputTextBoxReleaseFocusCon.disconnect();
dmUserInputProcessMouseEventCon.disconnect();
}
- (void) bindToUserInputService:(shared_ptr<RBX::DataModel>) dataModel
{
if( RBX::UserInputService* userInputService = RBX::ServiceProvider::find<RBX::UserInputService>(dataModel.get()) )
{
// code below will listen to UserInputService and detect when a textbox is in focus (so we can show the virtual keyboard)
dmUserInputTextBoxFocusCon = userInputService->textBoxGainFocus.connect( boostFuncFromSelector_1< boost::shared_ptr<RBX::Instance> >
(@selector(textBoxFocusGained:),self) );
dmUserInputTextBoxReleaseFocusCon = userInputService->textBoxReleaseFocus.connect( boostFuncFromSelector_1< boost::shared_ptr<RBX::Instance> >
(@selector(textBoxFocusLost:),self) );
// code below will listen to UserInputService when it fires a mouse event post event (bool tells whether the mouse event was used by app)
dmUserInputProcessMouseEventCon = userInputService->processedMouseEvent.connect( boostFuncFromSelector_3<bool, void*, const shared_ptr<RBX::InputObject>& >
(@selector(postMouseEventProcessed:inputObject:event:),self) );
}
}
- (void) setupInputControls
{
// how quickly (in seconds) a user has to tap the screen to have a mouse down/up gesture sent
tapSensitivity = 0.19f;
// how much a tap can move in pixels on screen
tapTouchMoveTolerance = 20;
// Subview initialization
CGRect correctRect = self.frame;
if(rbxInputView != nil)
{
[rbxInputView removeFromSuperview];
rbxInputView = nil;
}
rbxInputView = [[RbxInputView alloc] init:correctRect];
[self addSubview:rbxInputView];
[rbxInputView datamodelInit];
[[GameKeyboard sharedInstance] setParentView:self];
}
- (void) invalidateTapGesture:(id) oldTapTouch
{
if(oldTapTouch)
{
if( oldTapTouch == tapTouch )
tapTouch = nil;
}
else
tapTouch = nil;
}
-(UITouch*) checkTouchesForTap:(NSSet *)touches withEvent:(UIEvent *)event
{
if(tapTouch)
{
for(UITouch* touch in touches)
{
if(touch == tapTouch)
{
UITouch* retainTapTouch = tapTouch;
[self oneFingerSingleTap];
return retainTapTouch;
}
}
}
return nil;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (!tapTouch && [touches count] == 1)
{
tapTouch = [touches anyObject];
CGPoint startPoint = [tapTouch locationInView:self];
tapTouchBeginPos = G3D::Vector2(startPoint.x,startPoint.y);
[self performSelector:@selector(invalidateTapGesture:) withObject:[touches anyObject] afterDelay:tapSensitivity];
}
for (UITouch* touch in touches)
{
CGPoint currPoint = [touch locationInView:self];
mouseButton1Event->setInputState(RBX::InputObject::INPUT_STATE_BEGIN);
mouseButton1Event->setPosition(RBX::Vector3(currPoint.x,currPoint.y,0));
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* theTapTouch = [self checkTouchesForTap:touches withEvent:event];
for(UITouch* touch in touches)
{
CGPoint currPoint = [touch locationInView:self];
if(touch == theTapTouch)
[self invalidateTapGesture:nil];
else
{
mouseButton1Event->setInputState(RBX::InputObject::INPUT_STATE_END);
mouseButton1Event->setPosition(RBX::Vector3(currPoint.x,currPoint.y,0));
}
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self checkTapTouchMove:touches];
for(UITouch* touch in touches)
{
CGPoint currPoint = [touch locationInView:self];
mouseMoveEvent->setPosition(G3D::Vector3(currPoint.x, currPoint.y, 0));
}
}
-(void) checkTapTouchMove:(NSSet*) touchesSet
{
for(UITouch* touch in touchesSet)
{
if(tapTouch == touch)
{
CGPoint tapTouchLocationCGPoint = [tapTouch locationInView:self];
RBX::Vector2 tapTouchLocation = RBX::Vector2(tapTouchLocationCGPoint.x,tapTouchLocationCGPoint.y);
if((tapTouchLocation - tapTouchBeginPos).length() > tapTouchMoveTolerance)
[self invalidateTapGesture:nil];
break;
}
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
for(UITouch* touch in touches)
{
if(touch == tapTouch)
{
tapTouch = nil;
return;
}
}
}
- (void) oneFingerSingleTap
{
if(RBX::UserInputService* userInputService = [self getUserInputServiceForGameDataModel])
{
CGPoint tapPoint = [tapTouch locationInView:self];
tapTouch = nil;
shared_ptr<RBX::InputObject> eventDown = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(RBX::InputObject::TYPE_MOUSEBUTTON1,
RBX::InputObject::INPUT_STATE_BEGIN,
RBX::Vector3(tapPoint.x,tapPoint.y,0),
RBX::Vector3(0,0,0),
[self getGame]->getDataModel().get());
userInputService->processToolEvent(eventDown);
shared_ptr<RBX::InputObject> eventUp = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(RBX::InputObject::TYPE_MOUSEBUTTON1,
RBX::InputObject::INPUT_STATE_END,
RBX::Vector3(tapPoint.x,tapPoint.y,0),
RBX::Vector3(0,0,0),
[self getGame]->getDataModel().get());
userInputService->processToolEvent(eventUp);
}
}
@end
@@ -0,0 +1,35 @@
//
// GameKeyboard.h
// RobloxMobile
//
// Created by Ben Tkacheff on 10/23/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#pragma once
#import "ControlComponent.h"
#include "v8datamodel/Textbox.h"
@interface GameKeyboard : ControlComponent <UITextFieldDelegate>
{
UITextField* textView;
boost::shared_ptr<RBX::TextBox> currentTextBox;
}
- (id) init;
- (void) dealloc;
+(id) sharedInstance;
-(void) hideKeyboard;
-(bool) showKeyboard:(const char*) stringToShow;
-(bool) showKeyboardWithTextBox:(boost::shared_ptr<RBX::TextBox>) newTextBox;
-(void)keyboardWillChangeFrame:(NSNotification *) notification;
-(void)keyboardWillHide:(NSNotification *) notification;
-(NSString*) getText;
-(void) setDefaultString:(NSString*) defaultString;
-(void) setParentView:(UIView*) parentView;
@end
@@ -0,0 +1,191 @@
//
// GameKeyboard.m
// RobloxMobile
//
// Created by Ben Tkacheff on 10/23/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import "GameKeyboard.h"
#import "UIScreen+PortraitBounds.h"
#include "v8datamodel/InputObject.h"
#import "RobloxInfo.h"
#define TEXTVIEWHEIGHT 28
static void runExternalReleaseFocus(shared_ptr<RBX::TextBox> currentTextbox) {
if (currentTextbox.get())
currentTextbox->externalReleaseFocus(currentTextbox->getText().c_str(), false, shared_ptr<RBX::InputObject>());
}
@implementation GameKeyboard
+ (id)sharedInstance
{
static dispatch_once_t pred = 0;
static GameKeyboard *shared = nil;
dispatch_once(&pred, ^{ // Need to use GCD for thread-safe allocation
shared = [[GameKeyboard alloc] init];
});
return shared;
}
- (id) init
{
if (self = [super init])
{
currentTextBox = boost::shared_ptr<RBX::TextBox>();
CGRect bounds = [[UIScreen mainScreen] portraitBounds];
bounds = CGRectMake(0,0,bounds.size.height,bounds.size.width);
self.frame = bounds;
[self setUserInteractionEnabled:NO];
textView = [[UITextField alloc] initWithFrame:CGRectMake(5,bounds.size.height/2,bounds.size.width - 10,TEXTVIEWHEIGHT)];
textView.borderStyle = UITextBorderStyleRoundedRect;
textView.delegate = self;
textView.autocorrectionType = UITextAutocorrectionTypeNo;
textView.hidden = YES;
[self addSubview:textView];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
return self;
}
-(void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void) hideKeyboard
{
currentTextBox = boost::shared_ptr<RBX::TextBox>();
textView.text = @"";
textView.hidden = YES;
[self setUserInteractionEnabled:NO];
[textView resignFirstResponder];
}
-(void)keyboardWillHide:(NSNotification *) notification
{
if (RBX::Game* game = [self getGameFromControlView])
if (shared_ptr<RBX::DataModel> currDataModel = game->getDataModel())
if(currentTextBox && currentTextBox.get())
currDataModel->submitTask(boost::bind(runExternalReleaseFocus, currentTextBox), RBX::DataModelJob::TaskType::Write);
[self hideKeyboard];
}
-(void)keyboardWillChangeFrame:(NSNotification *) notification
{
if (![RobloxInfo isDeviceOSVersionPreiOS8])
{
dispatch_async(dispatch_get_main_queue(),^{
//use information pulled from the notification to reposition the keyboard
CGRect endFrame = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect textFrame = CGRectMake(0, endFrame.origin.y - TEXTVIEWHEIGHT, endFrame.size.width, TEXTVIEWHEIGHT);
[textView setFrame:textFrame];
});
}
}
-(void) setDefaultString:(NSString*) defaultString
{
textView.placeholder = defaultString;
}
-(void) setParentView:(UIView*) parentView
{
if(!parentView)
[self hideKeyboard];
[parentView addSubview:self];
}
- (bool) showKeyboard:(const char*) stringToShow
{
if(textView.hidden)
{
dispatch_async(dispatch_get_main_queue(), ^{
textView.text = [NSString stringWithUTF8String:stringToShow];
CGRect bounds = [[UIScreen mainScreen] portraitBounds];
bounds = CGRectMake(0,0,bounds.size.height,bounds.size.width);
int margin = 5;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
textView.frame = CGRectMake(margin,bounds.size.height/2.5f,bounds.size.width - (margin + margin),TEXTVIEWHEIGHT);
else
textView.frame = CGRectMake(margin,bounds.size.height/2,bounds.size.width - (margin + margin),TEXTVIEWHEIGHT);
textView.hidden = NO;
[self setUserInteractionEnabled:YES];
[textView becomeFirstResponder];
});
return YES;
}
return NO;
}
- (bool) showKeyboardWithTextBox:(boost::shared_ptr<RBX::TextBox>) newTextBox
{
if(textView.hidden && newTextBox)
{
currentTextBox = newTextBox;
return [self showKeyboard:currentTextBox->getBufferedText().c_str()];
}
return NO;
}
-(NSString*) getText
{
return textView.text;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (currentTextBox)
{
currentTextBox->setBufferedText([textView.text stringByReplacingCharactersInRange:range withString:string].UTF8String, range.location + string.length);
}
return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if(RBX::UserInputService* userInputService = RBX::ServiceProvider::find<RBX::UserInputService>(currentTextBox.get()))
if (!userInputService->showStatsBasedOnInputString([textView.text UTF8String]))
{
userInputService->textboxDidFinishEditing([textView.text UTF8String], true);
// make sure our ui calls happen on main thread
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self hideKeyboard];
});
}
return YES;
}
- (void) textFieldDidEndEditing:(UITextField *)textField
{
if(![textView isFirstResponder])
return;
if(RBX::UserInputService* userInputService = RBX::ServiceProvider::find<RBX::UserInputService>(currentTextBox.get()))
userInputService->textboxDidFinishEditing([textView.text UTF8String], false);
// make sure our ui calls happen on main thread
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self hideKeyboard];
});
}
@end
@@ -0,0 +1,13 @@
//
// GameView.h
// RobloxMobile
//
// Created by Ben Tkacheff on 11/21/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface GameView : UIView
@end
@@ -0,0 +1,50 @@
//
// GameView.m
// RobloxMobile
//
// Created by Ben Tkacheff on 11/21/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import "GameView.h"
@implementation GameView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.userInteractionEnabled = YES;
// Initialization code
}
return self;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
}
-(void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
}
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
@end
@@ -0,0 +1,37 @@
//
// GameViewController.h
// RobloxMobile
//
// Created by Ben Tkacheff on 11/20/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import "GameView.h"
#include <string>
#import "ControlView.h"
#import <AdColony/AdColony.h>
@interface GameViewController : UIViewController <UIWebViewDelegate, AdColonyAdDelegate>
{
GameView* gameView;
UIWebView* externalWebView;
UIButton* closeWebviewButton;
UIActivityIndicatorView* webViewActivityIndicator;
CGFloat webviewTweenTime;
}
-(void) playVideoAd;
-(void) resizeGameView;
-(void) openUrlWindow:(std::string) url;
-(void) closeUrlWindow;
-(void) closeUrlWindow:(id) sender;
- (void) addControlView:(ControlView*)controlView;
-(ControlView*) getControlView;
- (void) onAdColonyAdStartedInZone:(NSString *)zoneID;
- (void) onAdColonyAdAttemptFinished:(BOOL)shown inZone:(NSString *)zoneID;
@end
@@ -0,0 +1,308 @@
//
// GameViewController.m
// RobloxMobile
//
// Created by Ben Tkacheff on 11/20/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import "GameViewController.h"
#import "LoginManager.h"
#import "RobloxInfo.h"
#import "StoreManager.h"
#import "AppDelegate.h"
#import "UIScreen+PortraitBounds.h"
#import "PlaceLauncher.h"
#import "RobloxNotifications.h"
#include "v8datamodel/LoginService.h"
#include "v8datamodel/GuiService.h"
#include "v8datamodel/AdService.h"
#include "util/SoundService.h"
DYNAMIC_FASTSTRINGVARIABLE(AdColonyAppId, "app02d1db3451cc4b6b97");
DYNAMIC_FASTSTRINGVARIABLE(AdColonyZoneId, "vz073fd9a8cf0c447cbc");
@implementation GameViewController
{
ControlView* _controlView;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
webviewTweenTime = 0.3;
CGSize screenSize = [[UIScreen mainScreen] portraitBounds].size;
gameView = [[GameView alloc] initWithFrame:CGRectMake(0, 0, screenSize.height, screenSize.width)];
self.view = gameView;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleGoingBackgroundNotification:)
name:RBX_NOTIFY_GAME_START_LEAVING
object:nil ];
}
return self;
}
-(void) dealloc
{
if(externalWebView)
{
[externalWebView removeFromSuperview];
externalWebView = nil;
}
_controlView = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
gameView = nil;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSDictionary* dictionary = [NSDictionary dictionaryWithObjectsAndKeys: [RobloxInfo getUserAgentString], @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
}
-(void) resizeGameView
{
[gameView layoutSubviews];
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
-(BOOL)shouldAutorotate
{
return YES;
}
#ifdef __IPHONE_9_0
-(UIInterfaceOrientationMask) supportedInterfaceOrientations
#else
-(NSUInteger)supportedInterfaceOrientations
#endif
{
return UIInterfaceOrientationMaskLandscape;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
return (orientation == UIInterfaceOrientationLandscapeRight) ? UIInterfaceOrientationLandscapeRight : UIInterfaceOrientationLandscapeLeft;
}
- (void) addControlView:(ControlView*)controlView
{
_controlView = controlView;
[self.view addSubview:controlView];
}
-(ControlView*) getControlView
{
return _controlView;
}
-(void) playVideoAd
{
NSString* adColonyZoneId = [NSString stringWithUTF8String:DFString::AdColonyZoneId.c_str()];
if(ControlView* controlView = [self getControlView])
{
if(shared_ptr<RBX::Game> game = [controlView getGame])
{
if(RBX::DataModel* dm = game->getDataModel().get())
{
if(RBX::Soundscape::SoundService* soundService = RBX::ServiceProvider::find<RBX::Soundscape::SoundService>(dm))
{
soundService->muteAllChannels(true);
}
}
}
}
[AdColony playVideoAdForZone:adColonyZoneId withDelegate:self];
}
- (void) onAdColonyAdStartedInZone:(NSString *)zoneID
{
if (ControlView* controlView = [self getControlView])
{
[controlView setUserInteractionEnabled:false];
if (RbxInputView* inputView = [controlView getRbxInputView])
{
[inputView setUserInteractionEnabled:false];
[inputView cancelAllTouches];
}
}
}
- (void) onAdColonyAdAttemptFinished:(BOOL)shown inZone:(NSString *)zoneID
{
if(ControlView* controlView = [self getControlView])
{
[controlView setUserInteractionEnabled:true];
if (RbxInputView* inputView = [controlView getRbxInputView])
{
[inputView setUserInteractionEnabled:true];
}
if(shared_ptr<RBX::Game> game = [controlView getGame])
{
if(RBX::DataModel* dm = game->getDataModel().get())
{
if(RBX::Soundscape::SoundService* soundService = RBX::ServiceProvider::find<RBX::Soundscape::SoundService>(dm))
{
soundService->muteAllChannels(false);
}
if(RBX::AdService* adService = RBX::ServiceProvider::find<RBX::AdService>(dm))
{
adService->videoAdClosed(shown);
}
}
}
if (shown)
{
//todo: do some webcall here for revenue sharing/stat tracking
}
}
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
id storeManager = GetStoreMgr;
if ([storeManager isKindOfClass:[StoreManager class]])
{
if([storeManager checkForInAppPurchases:request navigationType:navigationType])
return NO;
}
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
if (webView == externalWebView && webViewActivityIndicator)
{
[webViewActivityIndicator setHidden:YES];
}
}
-(void) signalGuiServiceUrlWindowClosedOnDataModel:(RBX::DataModel*) dataModel
{
if(dataModel)
if(RBX::GuiService* guiService = RBX::ServiceProvider::find<RBX::GuiService>(dataModel))
guiService->urlWindowClosed();
}
-(void) closeUrlWindow:(id) sender
{
if(externalWebView)
{
UIWebView* tempWebView = externalWebView;
externalWebView = nil;
if(ControlView* controlView = [self getControlView])
if(shared_ptr<RBX::Game> game = [controlView getGame])
{
[self signalGuiServiceUrlWindowClosedOnDataModel:game->getDataModel().get()];
}
CGSize screenSize = [[UIScreen mainScreen] portraitBounds].size;
screenSize = CGSizeMake(screenSize.height, screenSize.width);
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:webviewTweenTime
delay:0
options:UIViewAnimationOptionTransitionNone
animations:^{
tempWebView.frame = CGRectMake(screenSize.width/2 - tempWebView.frame.size.width/2, screenSize.height, tempWebView.frame.size.width, tempWebView.frame.size.height);
}
completion:^(BOOL finished) {
[tempWebView removeFromSuperview];
}
];
});
}
}
-(void) closeUrlWindow
{
[self closeUrlWindow:nil];
}
-(void) openUrlWindow:(std::string) url
{
if(externalWebView)
return;
CGSize screenSize = [[UIScreen mainScreen] portraitBounds].size;
// switch so dimensions are in landscape
screenSize = CGSizeMake(screenSize.height, screenSize.width);
int webviewWidth = 660;
int webviewHeight = 400;
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
webviewWidth = screenSize.width;
webviewHeight = screenSize.height;
}
if (externalWebView == nil)
{
int closeButtonSize = 22;
int closeButtonOffset = 5;
dispatch_async(dispatch_get_main_queue(), ^{
externalWebView = [[UIWebView alloc] initWithFrame:CGRectMake(screenSize.width/2 - webviewWidth/2, screenSize.height, webviewWidth, webviewHeight)];
externalWebView.delegate = self;
externalWebView.userInteractionEnabled = YES;
externalWebView.scalesPageToFit = NO;
webViewActivityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[webViewActivityIndicator setHidden:NO];
[webViewActivityIndicator startAnimating];
[webViewActivityIndicator setFrame:CGRectMake(externalWebView.frame.size.width/2 - webViewActivityIndicator.frame.size.width/2,
externalWebView.frame.size.height/2 - webViewActivityIndicator.frame.size.height/2,
webViewActivityIndicator.frame.size.width,
webViewActivityIndicator.frame.size.height)];
closeWebviewButton = [[UIButton alloc] initWithFrame:CGRectMake(externalWebView.frame.size.width - closeButtonSize - closeButtonOffset, closeButtonOffset, closeButtonSize, closeButtonSize)];
[closeWebviewButton setImage:[UIImage imageNamed:@"Clear.png"] forState:UIControlStateNormal];
[closeWebviewButton addTarget:self action:@selector(closeUrlWindow:) forControlEvents:UIControlEventTouchUpInside];
[externalWebView addSubview:closeWebviewButton];
[externalWebView addSubview:webViewActivityIndicator];
if(ControlView* controlView = [self getControlView])
[controlView addSubview:externalWebView];
});
}
dispatch_async(dispatch_get_main_queue(), ^{
[externalWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]]]];
[UIView animateWithDuration:webviewTweenTime animations:^{
externalWebView.frame = CGRectMake(screenSize.width/2 - webviewWidth/2, screenSize.height/2 - webviewHeight/2, externalWebView.frame.size.width, externalWebView.frame.size.height);
}];
});
}
-(void) handleGoingBackgroundNotification:(NSNotification*) leaveGameNotification
{
if ([AdColony videoAdCurrentlyRunning])
[AdColony cancelAd];
}
@end
@@ -0,0 +1,93 @@
//
// RbxInputView.h
// Roblox iOS Shared Code
//
// Created by Ben Tkacheff on 10/25/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#pragma once
#import <GameController/GameController.h>
#import <CoreMotion/CoreMotion.h>
#import "ControlComponent.h"
#include "v8datamodel/TouchInputService.h"
#include "v8datamodel/UserInputService.h"
@class RbxInputView;
typedef std::vector<G3D::Vector3> KeycodeInputs;
typedef boost::unordered_map<RBX::KeyCode, KeycodeInputs> BufferedGamepadState;
typedef boost::unordered_map<RBX::InputObject::UserInputType, BufferedGamepadState> BufferedGamepadStates;
static boost::mutex ControllerBufferMutex;
@interface RbxInputView : ControlComponent <UIGestureRecognizerDelegate>
{
G3D::Vector2int16 windowSize;
// Gesture Input
// each direction needs its own recognizer, nice...
UISwipeGestureRecognizer *swipeRightRecognizer;
UISwipeGestureRecognizer *swipeLeftRecognizer;
UISwipeGestureRecognizer *swipeUpRecognizer;
UISwipeGestureRecognizer *swipeDownRecognizer;
UITapGestureRecognizer *tapRecognizer;
UITapGestureRecognizer *twoFingerTapRecognizer;
UITapGestureRecognizer *threeFingerTapRecognizer;
UIRotationGestureRecognizer* rotationRecognizer;
UILongPressGestureRecognizer* longPressRecognizer;
UIPinchGestureRecognizer *pinchRecognizer;
UIPanGestureRecognizer *panRecognizer;
// Touch Input
boost::unordered_map<void*, shared_ptr<RBX::InputObject> > touchInputMap;
NSMutableArray* storedTouches;
// Motion Input
CMMotionManager *motionManager;
NSOperationQueue *motionQueue;
CMAttitude* refAttitude;
// Controller Input
bool paused;
std::map<int, bool> controllersConnectedMap;
BufferedGamepadStates controllerBufferMap;
// DataModel service references
weak_ptr<RBX::TouchInputService> weakTouchInputService;
}
- (id) init: (CGRect)frame;
// any init that requires use of a datamodel or its services, put in this function
- (void) datamodelInit;
// Basic Input handling
-(void) sendTouchEvent:(UITouch*) touch;
-(void) sendTouchEvent:(UITouch*) touch shouldOverride:(BOOL) shouldOverride overrideState:(UITouchPhase) overrideState;
-(void) cancelAllTouches;
-(void) basicGestureConfig:(UIGestureRecognizer*) gesture;
// Gesture handlers
-(void) twoFingerPinch:(UIPinchGestureRecognizer *)recognizer;
-(void) tapGesture:(UITapGestureRecognizer*) recognizer;
-(void) swipeGesture:(UISwipeGestureRecognizer*)recognizer;
-(void) longPressGesture:(UILongPressGestureRecognizer*) recognizer;
-(void) panGesture:(UIPanGestureRecognizer*) recognizer;
-(RBX::UserInputService::SwipeDirection) getRbxSwipeDirection:(UISwipeGestureRecognizerDirection) uiSwipeDirection;
// Motion Stuff
-(void) startMotionUpdates;
+(BOOL) isGyroscopeAvailable;
// Controller Stuff
-(void) gcControllerConnected:(NSNotification*) notification;
-(void) gcControllerDisconnected:(NSNotification*) notification;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,74 @@
/*
File: KeychainItemWrapper.h
Abstract:
Objective-C wrapper for accessing a single keychain item.
Version: 1.2
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2010 Apple Inc. All Rights Reserved.
*/
#import <UIKit/UIKit.h>
/*
The KeychainItemWrapper class is an abstraction layer for the iPhone Keychain communication. It is merely a
simple wrapper to provide a distinct barrier between all the idiosyncracies involved with the Keychain
CF/NS container objects.
*/
@interface KeychainItemWrapper : NSObject
{
NSMutableDictionary *keychainItemData; // The actual keychain item data backing store.
NSMutableDictionary *genericPasswordQuery; // A placeholder for the generic keychain item query used to locate the item.
}
@property (nonatomic, retain) NSMutableDictionary *keychainItemData;
@property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery;
// Designated initializer.
- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
- (void)setObject:(id)inObject forKey:(id)key;
- (id)objectForKey:(id)key;
// Initializes and resets the default generic keychain item data.
- (void)resetKeychainItem;
@end
@@ -0,0 +1,306 @@
/*
File: KeychainItemWrapper.m
Abstract:
Objective-C wrapper for accessing a single keychain item.
Version: 1.2
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2010 Apple Inc. All Rights Reserved.
*/
#import "KeychainItemWrapper.h"
#import <Security/Security.h>
/*
These are the default constants and their respective types,
available for the kSecClassGenericPassword Keychain Item class:
kSecAttrAccessGroup - CFStringRef
kSecAttrCreationDate - CFDateRef
kSecAttrModificationDate - CFDateRef
kSecAttrDescription - CFStringRef
kSecAttrComment - CFStringRef
kSecAttrCreator - CFNumberRef
kSecAttrType - CFNumberRef
kSecAttrLabel - CFStringRef
kSecAttrIsInvisible - CFBooleanRef
kSecAttrIsNegative - CFBooleanRef
kSecAttrAccount - CFStringRef
kSecAttrService - CFStringRef
kSecAttrGeneric - CFDataRef
See the header file Security/SecItem.h for more details.
*/
@interface KeychainItemWrapper (PrivateMethods)
/*
The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was
to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the
Keychain API expects as a validly constructed container class.
*/
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;
// Updates the item in the keychain, or adds it if it doesn't exist.
- (void)writeToKeychain;
@end
@implementation KeychainItemWrapper
@synthesize keychainItemData, genericPasswordQuery;
- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
{
if (self = [super init])
{
// Begin Keychain search setup. The genericPasswordQuery leverages the special user
// defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
// items which may be included by the same application.
genericPasswordQuery = [[NSMutableDictionary alloc] init];
[genericPasswordQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[genericPasswordQuery setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
// The keychain access group attribute determines if this item can be shared
// amongst multiple apps whose code signing entitlements contain the same keychain access group.
if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[genericPasswordQuery setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
}
// Use the proper search constants, return only the attributes of the first match.
[genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
CFDictionaryRef outDictionary = nil;
if (SecItemCopyMatching((__bridge_retained CFDictionaryRef)tempQuery, (CFTypeRef*)&outDictionary) != noErr)
{
// Stick these default values into keychain item if nothing found.
[self resetKeychainItem];
// Add the generic attribute and the keychain access group.
[keychainItemData setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[keychainItemData setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
}
}
else
{
// load the saved data from Keychain.
self.keychainItemData = [self secItemFormatToDictionary:(__bridge NSDictionary *)(outDictionary)];
}
if(outDictionary)
CFRelease(outDictionary);
}
return self;
}
- (void)setObject:(id)inObject forKey:(id)key
{
if (inObject == nil) return;
id currentObject = [keychainItemData objectForKey:key];
if (![currentObject isEqual:inObject])
{
[keychainItemData setObject:inObject forKey:key];
[self writeToKeychain];
}
}
- (id)objectForKey:(id)key
{
return [keychainItemData objectForKey:key];
}
- (void)resetKeychainItem
{
OSStatus junk = noErr;
if (!keychainItemData)
{
self.keychainItemData = [[NSMutableDictionary alloc] init];
}
else if (keychainItemData)
{
NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData];
junk = SecItemDelete((__bridge CFDictionaryRef)tempDictionary);
NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );
}
// Default attributes for keychain item.
[keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrAccount];
[keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrLabel];
[keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrDescription];
// Default data for keychain item.
[keychainItemData setObject:@"" forKey:(__bridge id)kSecValueData];
}
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert
{
// The assumption is that this method will be called with a properly populated dictionary
// containing all the right key/value pairs for a SecItem.
// Create a dictionary to return populated with the attributes and data.
NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
// Add the Generic Password keychain item class attribute.
[returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// Convert the NSString to NSData to meet the requirements for the value type kSecValueData.
// This is where to store sensitive data that should be encrypted.
NSString *passwordString = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData];
[returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
return returnDictionary;
}
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert
{
// The assumption is that this method will be called with a properly populated dictionary
// containing all the right key/value pairs for the UI element.
// Create a dictionary to return populated with the attributes and data.
NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
// Add the proper search key and class attribute.
[returnDictionary setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// Acquire the password data from the attributes.
CFDataRef passwordData = NULL;
if (SecItemCopyMatching((__bridge CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr)
{
// Remove the search, class, and identifier key/value, we don't need them anymore.
[returnDictionary removeObjectForKey:(__bridge id)kSecReturnData];
// Add the password to the dictionary, converting from NSData to NSString.
NSData* passwordDataObjC = (__bridge NSData*) passwordData;
NSString *password = [[NSString alloc] initWithBytes:[passwordDataObjC bytes] length:[passwordDataObjC length] encoding:NSUTF8StringEncoding];
[returnDictionary setObject:password forKey:(__bridge id)kSecValueData];
}
else
{
// Don't do anything if nothing is found.
NSAssert(NO, @"Serious error, no matching item found in the keychain.\n");
}
if(passwordData) CFRelease(passwordData);
return returnDictionary;
}
- (void)writeToKeychain
{
CFDictionaryRef attributes = NULL;
NSMutableDictionary *updateItem = NULL;
OSStatus result;
if (SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)
{
// First we need the attributes from the Keychain.
updateItem = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary*) attributes];
// Second we need to add the appropriate search key/values.
[updateItem setObject:[genericPasswordQuery objectForKey:(__bridge id)kSecClass] forKey:(__bridge id)kSecClass];
// Lastly, we need to set up the updated attribute list being careful to remove the class.
NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];
[tempCheck removeObjectForKey:(__bridge id)kSecClass];
#if TARGET_IPHONE_SIMULATOR
// Remove the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
//
// The access group attribute will be included in items returned by SecItemCopyMatching,
// which is why we need to remove it before updating the item.
[tempCheck removeObjectForKey:(id)kSecAttrAccessGroup];
#endif
// An implicit assumption is that you can only update a single item at a time.
result = SecItemUpdate((__bridge CFDictionaryRef)updateItem, (__bridge CFDictionaryRef)tempCheck);
NSAssert( result == noErr, @"Couldn't update the Keychain Item." );
}
else
{
// No previous item found; add the new one.
result = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);
NSAssert( result == noErr, @"Couldn't add the Keychain Item." );
}
}
@end
+92
View File
@@ -0,0 +1,92 @@
//
// LoginManager.h
// RobloxMobile
//
// Created by Ben Tkacheff on 4/19/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <GigyaSDK/Gigya.h>
#import "NonRotatableNavigationController.h"
#import "RBXEventReporter.h"
#define ENCODED_PW_KEY @"encodedPassword"
#define ENCODED_USERNAME_KEY @"encodedUserName"
#define NONENCODED_PW_KEY @"NonEncodedPassword"
typedef void(^LoginManagerCompletionBlock)(NSError *loginError);
typedef void(^SocialLoginCompletionBlock)(bool success, NSString *message);
@interface LoginManager : NSObject
+(id) sharedInstance;
+ (BOOL) apiProxyEnabled;
#pragma mark - odd Captcha functions
+(NonRotatableNavigationController*) CaptchaForLoginWithUsername:(NSString*)username
andV1Completion:(SocialLoginCompletionBlock)completion1
andV2Completion:(LoginManagerCompletionBlock)completion2;
+(NonRotatableNavigationController*) CaptchaForSignupWithUsername:(NSString*)username
andV1Completion:(SocialLoginCompletionBlock)completion1
andV2Completion:(LoginManagerCompletionBlock)completion2;
+(NonRotatableNavigationController*) CaptchaForSocialSignupWithUsername:(NSString*)username
andV1Completion:(SocialLoginCompletionBlock)completion1
andV2Completion:(LoginManagerCompletionBlock)completion2;
#pragma mark - Life cycle functions
-(void) applicationWillTerminate;// call this method as application is going to terminate (saves info for next session)
#pragma mark - Accessors and Mutators
-(BOOL) getRememberPassword;
-(void) setRememberPassword:(BOOL) shouldRemember;
-(BOOL) hasLoginCredentials;
-(BOOL) hasSocialLoginCredentials;
+(BOOL) sessionLoginEnabled;
#pragma mark - Odd functions
-(void) processBackground;
#pragma mark - Social Functions
-(bool) isFacebookEnabled;
+(NSString*) ProviderNameFacebook;
+(NSString*) ProviderNameTwitter;
+(NSString*) ProviderNameGooglePlus;
-(void) doSocialLoginFromController:(UIViewController*)aController
forProvider:(NSString*)providerName
withCompletion:(SocialLoginCompletionBlock)completionHandler;
-(void) doSocialSignupWithUsername:(NSString*)username
gigyaID:(NSString*)gigyaUID
birthday:(NSString*)birthdayString
gender:(NSString*)playerGender
email:(NSString*)playerEmail
completion:(SocialLoginCompletionBlock)handler;
-(void) doSocialLogout;
-(void) doSocialFetchGigyaInfoWithUID:(NSString*)gigyaUID isLoggingIn:(bool)isLogin withCompletion:(SocialLoginCompletionBlock)completionHandler;
-(void) doSocialUpdateInfoFromContext:(RBXAnalyticsContextName)context forProvider:providerName withCompletion:(SocialLoginCompletionBlock)handler;
-(void) doSocialConnect:(UIViewController*)aController
toProvider:(NSString*)providerName
withCompletion:(void(^)(bool success, NSString* message))completionHandler;
-(void) doSocialDisconnect:(NSString*)providerName withCompletion:(SocialLoginCompletionBlock)handler;
-(void) doSocialNotifyGigyaLoginWithContext:(RBXAnalyticsContextName)context withCompletion:(SocialLoginCompletionBlock)completion;
#pragma mark - Game Center
-(void) doGameCenterLogin;
-(void) updateGameCenterUser;
#pragma mark - Login/Out Functions
-(void) processStartupAutoLogin:(LoginManagerCompletionBlock)autoLoginCompletionBlock;
-(void) loginWithUsername:(NSString*) username password:(NSString*)password completionBlock:(LoginManagerCompletionBlock)completionBlock;
-(void) logoutRobloxUser;
- (void) getUserAccountInfoForContext:(RBXAnalyticsContextName)context completionHandler:(LoginManagerCompletionBlock)loginV2CompletionHandler;
#pragma mark - Cookie Management
+(NSArray *) cookies;
+(void) initializeCookieManagementPolicy;
+(void) updateCookiesInAppHttpLayer;
+(void) clearAllRobloxCookie;
+(void) printCookies;
@end
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
//
// MainViewController.h
// IOSClient
//
// Created by Ben Tkacheff on 9/20/12.
// Copyright (c) 2012 tolkring@gmail.com. All rights reserved.
//
#import <UIKit/UIKit.h>
#include "RobloxView.h"
@class GameViewController;
@interface MainViewController : UIViewController
{
UIWindow* ogreWindow;
UIView* ogreView;
GameViewController* ogreViewController;
RobloxView* rbxView;
UIViewController* lastNonGameController;
}
+ (id)sharedInstance;
-(void) switchView:(UIView*) newView;
-(void) addSubview:(UIView*) view;
-(UIWindow*) getOgreWindow;
-(void) setOgreWindow:(UIWindow*) value;
-(UIView*) getOgreView;
-(void) setOgreView:(UIView*) value;
-(GameViewController*) getOgreViewController;
-(void) setOgreViewController:(GameViewController*) value;
-(void) setRobloxView:(RobloxView*) newRbxView;
-(RobloxView*) getRobloxView;
-(void) setLastNonGameController:(UIViewController*) lastController;
-(UIViewController*) getLastNonGameController;
-(void) debugPrint;
@end
+111
View File
@@ -0,0 +1,111 @@
//
// MainViewController.m
// IOSClient
//
// Created by Ben Tkacheff on 9/20/12.
// Copyright (c) 2012 tolkring@gmail.com. All rights reserved.
//
#import "MainViewController.h"
@implementation MainViewController
+ (id)sharedInstance
{
static dispatch_once_t mainViewPred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&mainViewPred, ^{ // Need to use GCD for thread-safe allocation
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-(void) switchView:(UIView*) newView
{
self.view = newView;
}
-(void) addSubview:(UIView*) view
{
if(self.view)
[self.view addSubview:view];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
-(UIWindow*) getOgreWindow
{
return ogreWindow;
}
-(void) setOgreWindow:(UIWindow*) value
{
ogreWindow = value;
}
-(UIView*) getOgreView
{
return ogreView;
}
-(void) setOgreView:(UIView*) value
{
ogreView = value;
}
-(void) setRobloxView:(RobloxView*) newRbxView
{
rbxView = newRbxView;
}
-(RobloxView*) getRobloxView
{
return rbxView;
}
-(GameViewController*) getOgreViewController
{
return ogreViewController;
}
-(void) setOgreViewController:(GameViewController*) value
{
ogreViewController = value;
}
-(void) setLastNonGameController:(UIViewController*) lastController
{
lastNonGameController = lastController;
}
-(UIViewController*) getLastNonGameController
{
return lastNonGameController;
}
-(void) debugPrint
{
NSLog(@"MainViewController::debugPrint");
NSLog(@"ogreWindow %@", ogreWindow);
NSLog(@"ogreView %@", ogreView);
NSLog(@"ogreViewController %@", ogreViewController);
NSLog(@"rbxView %p", rbxView);
NSLog(@"lastNonGameController %@", lastNonGameController);
}
@end
+21
View File
@@ -0,0 +1,21 @@
//
// Roblox.h
// RobloxMobile
//
// Created by Ben Tkacheff on 1/30/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#include <Foundation/Foundation.h>
@interface FunctionMarshaller : NSObject
{
@public void* pClosure;
}
@property (nonatomic,assign) void* pClosure;
- (void)marshallFunction;
@end
+59
View File
@@ -0,0 +1,59 @@
//
// Roblox.m
// RobloxMobile
//
// Created by Ben Tkacheff on 1/30/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import "MarshallerInterface.h"
#include "FunctionMarshaller.h"
#include "Roblox.h"
@implementation FunctionMarshaller
@synthesize pClosure;
-(void) marshallFunction
{
RBX::FunctionMarshaller::handleAppEvent(pClosure);
}
@end
static NSString* kAppEventMode = @"RobloxAppEvent";
static NSArray* kAppEventModeList = [NSArray arrayWithObjects:(NSString*)kCFRunLoopDefaultMode, kAppEventMode, nil];
void Roblox::sendAppEvent(void *pClosure)
{
RBX::CEvent *waitEvent = ((RBX::FunctionMarshaller::Closure *) pClosure)->waitEvent;
BOOL waitFlag = (waitEvent == NULL);
FunctionMarshaller* controller = [[FunctionMarshaller alloc] init];
controller->pClosure = pClosure;
[controller performSelectorOnMainThread:@selector(marshallFunction) withObject:nil waitUntilDone:waitFlag modes:kAppEventModeList];
if (waitEvent)
{
waitEvent->Wait();
}
}
void Roblox::postAppEvent(void *pClosure)
{
FunctionMarshaller* controller = [[FunctionMarshaller alloc] init];
controller->pClosure = pClosure;
[controller performSelectorOnMainThread:@selector(marshallFunction) withObject:nil waitUntilDone:NO modes:kAppEventModeList];
}
void Roblox::processAppEvents()
{
while (CFRunLoopRunInMode((CFStringRef)kAppEventMode, 0, true) == kCFRunLoopRunHandledSource)
;
}
+27
View File
@@ -0,0 +1,27 @@
//
// NotificationHelper.h
// RobloxMobile
//
// Created by Ganesh on 5/14/14.
// Copyright (c) 2014 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UILocalNotification.h>
#include <string>
#include <boost/function.hpp>
#include "reflection/Type.h"
@interface NotificationHelper : NSObject
+ (id) sharedInstance;
-(void)scheduleLocalNotification:(int)userId withAlertID:(int) alertID withAlertMessage:(std::string) alertMessage showInMinutesFromNow:(int) minutesFromNow;
-(void)cancelAllNotification:(int)userId;
-(void)cancelNotification:(int)userId withAlertID:(int) alertID;
-(void)handleNotification:(UILocalNotification*)notification;
-(void)getScheduledNotifications :(int) userId
withResumeFunction :(boost::function<void(shared_ptr<const RBX::Reflection::ValueArray>)>) resumeFn
withErrorFunction :(boost::function<void(std::string)>) errFn;
@end
+190
View File
@@ -0,0 +1,190 @@
//
// NotificationHelper.m
// RobloxMobile
//
// Created by Ganesh on 5/14/14.
// Copyright (c) 2014 ROBLOX. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "NotificationHelper.h"
#import "RobloxWebUtility.h"
#import <UIKit/UIKit.h>
@implementation NotificationHelper
+(id) sharedInstance {
static dispatch_once_t onceToken = 0;
__strong static id _sharedObject = nil;
dispatch_once(&onceToken, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-(id) init
{
if(self = [super init])
{
}
return self;
}
- (void)scheduleLocalNotification:(int)userId withAlertID:(int) alertID withAlertMessage:(std::string) alertMessage showInMinutesFromNow:(int) minutesFromNow
{
#ifdef STANDALONE_NOTIFICATION
UILocalNotification *localNotification = [[[UILocalNotification alloc] init] autorelease];
NSDate *currentDate = [NSDate date];
NSDate *dateToFire = [currentDate dateByAddingTimeInterval:60*minutesFromNow];
localNotification.fireDate = dateToFire;
localNotification.alertBody = [NSString stringWithFormat:@"%s", alertMessage.c_str()];
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.applicationIconBadgeNumber = [[[UIApplication sharedApplication] scheduledLocalNotifications] count] + 1;
//localNotification.applicationIconBadgeNumber = 1; // set the badge number to 1 similar to what other Game Apps do
NSDictionary *infoDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%d", userId], @"UserId", [NSString stringWithFormat:@"%d", alertID], @"AlertID", nil];
localNotification.userInfo = infoDict;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
[self makeRoomForNewNotificationForUserId:userId];
#endif
}
-(void)cancelNotification:(int)userId withAlertID:(int) alertID
{
#ifdef STANDALONE_NOTIFICATION
for(UILocalNotification *aNotif in [[UIApplication sharedApplication] scheduledLocalNotifications])
{
if([[aNotif.userInfo objectForKey:@"UserId"] isEqualToString:[NSString stringWithFormat:@"%d", userId]] && [[aNotif.userInfo objectForKey:@"AlertID"] isEqualToString:[NSString stringWithFormat:@"%d", alertID]] )
{
[[UIApplication sharedApplication] cancelLocalNotification:aNotif];
}
}
#endif
}
-(void)cancelAllNotification:(int)userId
{
#ifdef STANDALONE_NOTIFICATION
for(UILocalNotification *aNotif in [[UIApplication sharedApplication] scheduledLocalNotifications])
{
if([[aNotif.userInfo objectForKey:@"UserId"] isEqualToString:[NSString stringWithFormat:@"%d", userId]])
{
[[UIApplication sharedApplication] cancelLocalNotification:aNotif];
}
}
#endif
}
-(void) handleNotification:(UILocalNotification*)notification
{
#ifdef STANDALONE_NOTIFICATION
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
if (notification)
{
[self renumberBadgesOfPendingNotifications];
// TO DO what do we need to do with this, we may later on want to launch the placeid
}
#endif
}
// Why : http://stackoverflow.com/questions/5962054/iphone-incrementing-the-application-badge-through-a-local-notification
- (void)renumberBadgesOfPendingNotifications
{
// clear the badge on the icon
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
// first get a copy of all pending notifications (unfortunately you cannot 'modify' a pending notification)
NSSortDescriptor * fireDateDesc = [NSSortDescriptor sortDescriptorWithKey:@"fireDate" ascending:YES];
NSArray * pendingNotifications = [[[UIApplication sharedApplication] scheduledLocalNotifications] sortedArrayUsingDescriptors:@[fireDateDesc]];
// if there are any pending notifications -> adjust their badge number
if (pendingNotifications.count != 0)
{
// clear all pending notifications
[[UIApplication sharedApplication] cancelAllLocalNotifications];
// the for loop will 'restore' the pending notifications, but with corrected badge numbers
// note : a more advanced method could 'sort' the notifications first !!!
NSUInteger badgeNbr = 1;
for (UILocalNotification *notification in pendingNotifications)
{
// modify the badgeNumber
notification.applicationIconBadgeNumber = badgeNbr++;
// schedule 'again'
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
}
}
-(void)getScheduledNotifications :(int) userId
withResumeFunction :(boost::function<void(shared_ptr<const RBX::Reflection::ValueArray>)>) resumeFn
withErrorFunction :(boost::function<void(std::string)>) errFn
{
#ifdef STANDALONE_NOTIFICATION
NSSortDescriptor * fireDateDesc = [NSSortDescriptor sortDescriptorWithKey:@"fireDate" ascending:YES];
NSArray * pendingNotifications = [[[UIApplication sharedApplication] scheduledLocalNotifications] sortedArrayUsingDescriptors:@[fireDateDesc]];
shared_ptr<RBX::Reflection::ValueArray> alertIds(rbx::make_shared<RBX::Reflection::ValueArray>());
for (UILocalNotification *notification in pendingNotifications)
{
int alertId = [[notification.userInfo objectForKey:@"AlertID"] intValue];
alertIds->push_back(alertId);
}
resumeFn(alertIds);
#endif
}
-(void)makeRoomForNewNotificationForUserId:(int) userId
{
NSSortDescriptor * fireDateDesc = [NSSortDescriptor sortDescriptorWithKey:@"fireDate" ascending:YES];
NSArray * pendingNotifications = [[[UIApplication sharedApplication] scheduledLocalNotifications] sortedArrayUsingDescriptors:@[fireDateDesc]];
if (pendingNotifications.count != 0)
{
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
int maxNotificationsPerUser = iosSettings->GetValueMaxLocalNotificationsPerUserID();
int maxNotifications = iosSettings->GetValueMaxLocalNotifications();
int numNotificationsForUserId = 0;
int numTotalNotifications = 0;
for(UILocalNotification *aNotif in [[UIApplication sharedApplication] scheduledLocalNotifications])
{
++numTotalNotifications;
if([[aNotif.userInfo objectForKey:@"UserId"] isEqualToString:[NSString stringWithFormat:@"%d", userId]])
{
++numNotificationsForUserId;
if (numNotificationsForUserId > maxNotificationsPerUser)
{
[[UIApplication sharedApplication] cancelLocalNotification:aNotif];
--numNotificationsForUserId;
--numTotalNotifications;
}
}
else
{
if (numTotalNotifications > maxNotifications)
{
[[UIApplication sharedApplication] cancelLocalNotification:aNotif];
--numTotalNotifications;
}
}
}
}
}
@end
+74
View File
@@ -0,0 +1,74 @@
//
// ObjectiveCUtilities.h
// RobloxMobile
//
// Created by Ben Tkacheff on 11/1/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#include "boost/function.hpp"
#include "boost/bind.hpp"
static boost::function<void()> boostFuncFromSelector(SEL selector, id delegate)
{
typedef void (*func)(id, SEL);
func impl = (func)[delegate methodForSelector:selector];
return boost::bind(impl, delegate, selector);
}
template<typename T>
static boost::function<void(T)> boostFuncFromSelector_1(SEL selector, id delegate)
{
typedef void (*func)(id, SEL, T);
func impl = (func)[delegate methodForSelector:selector];
return boost::bind(impl, delegate, selector, _1);
}
template<typename T>
static boost::function<void(T)> boostFuncFromSelector_1(SEL selector, T passedArg, id delegate)
{
typedef void (*func)(id, SEL, T);
func impl = (func)[delegate methodForSelector:selector];
return boost::bind(impl, delegate, selector, passedArg);
}
template<typename T, typename T2>
static boost::function<void(T,T2)> boostFuncFromSelector_2(SEL selector, id delegate)
{
typedef void (*func)(id, SEL, T, T2);
func impl = (func)[delegate methodForSelector:selector];
return boost::bind(impl, delegate, selector, _1, _2);
}
template<typename T, typename T2>
static boost::function<void(T,T2)> boostFuncFromSelector_2(SEL selector, T passedArg1, id delegate)
{
typedef void (*func)(id, SEL, T, T2);
func impl = (func)[delegate methodForSelector:selector];
return boost::bind(impl, delegate, selector, passedArg1, _2);
}
template<typename T, typename T2>
static boost::function<void(T,T2)> boostFuncFromSelector_2(SEL selector, T passedArg1, T2 passedArg2, id delegate)
{
typedef void (*func)(id, SEL, T, T2);
func impl = (func)[delegate methodForSelector:selector];
return boost::bind(impl, delegate, selector, passedArg1, passedArg2);
}
template<typename T, typename T2, typename T3>
static boost::function<void(T,T2, T3)> boostFuncFromSelector_3(SEL selector, id delegate)
{
typedef void (*func)(id, SEL, T, T2, T3);
func impl = (func)[delegate methodForSelector:selector];
return boost::bind(impl, delegate, selector, _1, _2, _3);
}
template<typename T, typename T2, typename T3, typename T4>
static boost::function<void(T,T2, T3, T4)> boostFuncFromSelector_4(SEL selector, id delegate)
{
typedef void (*func)(id, SEL, T, T2, T3, T4);
func impl = (func)[delegate methodForSelector:selector];
return boost::bind(impl, delegate, selector, _1, _2, _3, _4);
}
+113
View File
@@ -0,0 +1,113 @@
//
// PlaceLauncher.h
// RobloxMobile
//
// Created by David York on 9/25/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#pragma once
#import <Foundation/Foundation.h>
#import <UIKit/UIViewController.h>
#include <boost/filesystem.hpp>
#include <boost/iostreams/copy.hpp>
#include "v8datamodel/Game.h"
#include "rbx/signal.h"
#include "v8tree/Instance.h"
enum JoinGameRequest {
JOIN_GAME_REQUEST_PLACEID,
JOIN_GAME_REQUEST_USERID,
JOIN_GAME_REQUEST_PRIVATE_SERVER,
JOIN_GAME_REQUEST_GAME_INSTANCE
};
class Teleporter;
class RobloxView;
@class GameViewController;
@interface RBXGameLaunchParams : NSObject
@property int targetId;
@property JoinGameRequest joinRequestType;
@property (retain, nonatomic) NSString* joinRequestString;
@property (retain, nonatomic) NSString* accessCode;
@property (retain, nonatomic) NSString* gameInstanceId;
+(RBXGameLaunchParams*) InitParamsForFollowUser:(int)userID;
+(RBXGameLaunchParams*) InitParamsForJoinPlace:(int)placeID;
+(RBXGameLaunchParams*) InitParamsForJoinPrivateServer:(int)placeID withAccessCode:(NSString*)vpsAccessCode;
+(RBXGameLaunchParams*) InitParamsForJoinGameInstance:(int)placeID withInstanceID:(NSString*)instanceID;
-(std::string) getStringJoinURL;
@end
@interface PlaceLauncher : NSObject
{
RobloxView* rbxView;
BOOL isCurrentlyPlayingGame;
BOOL isLeavingGame;
int lastPlaceId;
boost::scoped_ptr<Teleporter> teleporter;
BOOL hasReceivedMemoryWarning;
// player join tracking
rbx::signals::connection childConnection;
rbx::signals::connection playerConnection;
shared_ptr<RBX::Game> currentGame;
}
+ (id)sharedInstance;
// game creation/destruction functions
-(BOOL) startGameLocal:(int)portId ipAddress:(NSString*) ipAddress controller:(UIViewController*)lastNonGameController presentGameAutomatically:(BOOL) presentGameAutomatically userId:(int) userId;
-(BOOL) startGame:(RBXGameLaunchParams*)params controller:(UIViewController*)lastNonGameController presentGameAutomatically:(BOOL)presentGameAutomatically;
-(BOOL) startGameWithJoinScript:(NSString*)joinScript controller:(UIViewController*)lastNonGameController presentGameAutomatically:(BOOL) presentGameAutomatically;
-(void) injectJoinScript:(NSString*)joinUrlScript;
-(BOOL) appActive;
-(void) leaveGame;
-(void) leaveGame:(BOOL) userRequestedLeave;
-(void) leaveGameShutdown:(BOOL) userRequestedLeave;
-(void) createGame:(shared_ptr<RBX::Game>)game presentGameAutomatically:(BOOL) presentGameAutomatically;
-(void) finishGameSetup:(boost::shared_ptr<RBX::Game>)game gameViewController:(GameViewController*) gameController;
-(void) deleteRobloxView:(BOOL) resetCurrentGame;
-(void) presentGameViewController;
-(void)teleport:(NSString*)ticket withAuthentication:(NSString*)url withScript:(NSString*)script;
// iOS events
-(void) applicationDidReceiveMemoryWarning;
-(void) clearCachedContent;
-(void) disableViewBecauseGoingToBackground;
-(void) enableViewBecauseGoingToForeground;
// game setup functions
-(void) placeDidFinishLoading;
-(void) setLastNonGameController:(UIViewController*)lastNonGameController needsOnline:(BOOL) needsOnline;
-(void) handleStartGameFailure;
// game state functions
-(BOOL) getIsCurrentlyPlayingGame;
// utility functions
-(void) checkPlacePartCount;
-(void) setLastPlaceId:(int) lastId;
// player join tracking
-(void) childAdded:(shared_ptr<RBX::Instance>)child;
-(void) playerLoaded:(shared_ptr<RBX::Instance>)child;
-(void) closeChildConnections;
// current game
-(shared_ptr<RBX::Game>) getCurrentGame;
@end
File diff suppressed because it is too large Load Diff
+88
View File
@@ -0,0 +1,88 @@
/*
File: Reachability.h
Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs.
Version: 2.2
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and your
use, installation, modification or redistribution of this Apple software
constitutes acceptance of these terms. If you do not agree with these terms,
please do not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and subject
to these terms, Apple grants you a personal, non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple Software"), to
use, reproduce, modify and redistribute the Apple Software, with or without
modifications, in source and/or binary forms; provided that if you redistribute
the Apple Software in its entirety and without modifications, you must retain
this notice and the following text and disclaimers in all such redistributions
of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may be used
to endorse or promote products derived from the Apple Software without specific
prior written permission from Apple. Except as expressly stated in this notice,
no other rights or licenses, express or implied, are granted by Apple herein,
including but not limited to any patent rights that may be infringed by your
derivative works or by other works in which the Apple Software may be
incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2010 Apple Inc. All Rights Reserved.
*/
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
typedef enum {
NotReachable = 0,
ReachableViaWiFi,
ReachableViaWWAN
} NetworkStatus;
#define kReachabilityChangedNotification @"kNetworkReachabilityChangedNotification"
@interface Reachability: NSObject
{
BOOL localWiFiRef;
SCNetworkReachabilityRef reachabilityRef;
}
//reachabilityWithHostName- Use to check the reachability of a particular host name.
+ (Reachability*) reachabilityWithHostName: (NSString*) hostName;
//reachabilityWithAddress- Use to check the reachability of a particular IP address.
+ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress;
//reachabilityForInternetConnection- checks whether the default route is available.
// Should be used by applications that do not connect to a particular host
+ (Reachability*) reachabilityForInternetConnection;
//reachabilityForLocalWiFi- checks whether a local wifi connection is available.
+ (Reachability*) reachabilityForLocalWiFi;
//Start listening for reachability notifications on the current run loop
- (BOOL) startNotifier;
- (void) stopNotifier;
- (NetworkStatus) currentReachabilityStatus;
//WWAN may be available, but not active until a connection has been established.
//WiFi may require a connection for VPN on Demand.
- (BOOL) connectionRequired;
@end
+269
View File
@@ -0,0 +1,269 @@
/*
File: Reachability.m
Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs.
Version: 2.2
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and your
use, installation, modification or redistribution of this Apple software
constitutes acceptance of these terms. If you do not agree with these terms,
please do not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and subject
to these terms, Apple grants you a personal, non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple Software"), to
use, reproduce, modify and redistribute the Apple Software, with or without
modifications, in source and/or binary forms; provided that if you redistribute
the Apple Software in its entirety and without modifications, you must retain
this notice and the following text and disclaimers in all such redistributions
of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may be used
to endorse or promote products derived from the Apple Software without specific
prior written permission from Apple. Except as expressly stated in this notice,
no other rights or licenses, express or implied, are granted by Apple herein,
including but not limited to any patent rights that may be infringed by your
derivative works or by other works in which the Apple Software may be
incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2010 Apple Inc. All Rights Reserved.
*/
#import <sys/socket.h>
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
#import <CoreFoundation/CoreFoundation.h>
#import "Reachability.h"
#define kShouldPrintReachabilityFlags 1
static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment)
{
#if kShouldPrintReachabilityFlags
NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n",
(flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
(flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
(flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
(flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
(flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
(flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-',
comment
);
#endif
}
@implementation Reachability
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
#pragma unused (target, flags)
NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback");
NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback");
Reachability* noteObject = (__bridge Reachability*) info;
dispatch_async(dispatch_get_main_queue(), ^
{
// Post a notification to notify the client that the network reachability changed.
[[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject];
});
}
- (BOOL) startNotifier
{
BOOL retVal = NO;
SCNetworkReachabilityContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context))
{
if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
{
retVal = YES;
}
}
return retVal;
}
- (void) stopNotifier
{
if(reachabilityRef!= NULL)
{
SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
}
- (void) dealloc
{
[self stopNotifier];
if(reachabilityRef!= NULL)
{
CFRelease(reachabilityRef);
}
}
+ (Reachability*) reachabilityWithHostName: (NSString*) hostName;
{
Reachability* retVal = NULL;
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
if(reachability!= NULL)
{
retVal= [[self alloc] init];
if(retVal!= NULL)
{
retVal->reachabilityRef = reachability;
retVal->localWiFiRef = NO;
}
}
return retVal;
}
+ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress;
{
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
Reachability* retVal = NULL;
if(reachability!= NULL)
{
retVal= [[self alloc] init];
if(retVal!= NULL)
{
retVal->reachabilityRef = reachability;
retVal->localWiFiRef = NO;
}
}
return retVal;
}
+ (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 <netinet/in.h> as 169.254.0.0
localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
Reachability* retVal = [self reachabilityWithAddress: &localWifiAddress];
if(retVal!= NULL)
{
retVal->localWiFiRef = YES;
}
return retVal;
}
#pragma mark Network Flag Handling
- (NetworkStatus) localWiFiStatusForFlags: (SCNetworkReachabilityFlags) flags
{
PrintReachabilityFlags(flags, "localWiFiStatusForFlags");
BOOL retVal = NotReachable;
if((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect))
{
retVal = ReachableViaWiFi;
}
return retVal;
}
- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags
{
PrintReachabilityFlags(flags, "networkStatusForFlags");
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
{
// if target host is not reachable
return NotReachable;
}
BOOL retVal = NotReachable;
if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
{
// if target host is reachable and no connection is required
// then we'll assume (for now) that your on Wi-Fi
retVal = ReachableViaWiFi;
}
if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
{
// ... and the connection is on-demand (or on-traffic) if the
// calling application is using the CFSocketStream or higher APIs
if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
{
// ... and no [user] intervention is needed
retVal = ReachableViaWiFi;
}
}
if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
{
// ... but WWAN connections are OK if the calling application
// is using the CFNetwork (CFSocketStream?) APIs.
retVal = ReachableViaWWAN;
}
return retVal;
}
- (BOOL) connectionRequired;
{
NSAssert(reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
{
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
}
- (NetworkStatus) currentReachabilityStatus
{
NSAssert(reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef");
NetworkStatus retVal = NotReachable;
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
{
if(localWiFiRef)
{
retVal = [self localWiFiStatusForFlags: flags];
}
else
{
retVal = [self networkStatusForFlags: flags];
}
}
return retVal;
}
@end
+10
View File
@@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>
@interface RobloxAlert : NSObject
+(void) RobloxAlertWithMessage:(NSString*) message;
+(void) RobloxOKAlertWithMessageAndDelegate:(NSString*) message Delegate:(id) delegate;
+(void) RobloxAlertWithMessageAndDelegate:(NSString*) message Delegate:(id) delegate;
@end
+41
View File
@@ -0,0 +1,41 @@
#import "RobloxAlert.h"
#import <UIKit/UIKit.h>
@implementation RobloxAlert
+(void) RobloxAlertWithMessage:(NSString*) message
{
dispatch_async(dispatch_get_main_queue(),^{
/* open an alert with an OK button */
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"RobloxWord", @"")
message:message
delegate:nil
cancelButtonTitle:NSLocalizedString(@"OkWord", nil)
otherButtonTitles: nil];
[alert show];
});
}
+(void) RobloxAlertWithMessageAndDelegate:(NSString*) message Delegate:(id) delegate
{
dispatch_async(dispatch_get_main_queue(),^{
/* open an alert with OK and Cancel buttons */
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"RobloxWord", @"")
message:message
delegate:delegate
cancelButtonTitle:NSLocalizedString(@"CancelWord", nil)
otherButtonTitles:NSLocalizedString(@"OkWord", nil), nil];
[alert show];
});
}
+(void) RobloxOKAlertWithMessageAndDelegate:(NSString*) message Delegate:(id) delegate
{
dispatch_async(dispatch_get_main_queue(),^{
/* open an alert with an OK button */
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"RobloxWord", @"")
message:message
delegate:delegate
cancelButtonTitle:NSLocalizedString(@"OkWord", nil)
otherButtonTitles: nil];
[alert show];
});
}
@end
+31
View File
@@ -0,0 +1,31 @@
//
// RobloxCachedFlags.h
// RobloxMobile
//
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#pragma once
#include <Foundation/Foundation.h>
@interface RobloxCachedFlags : NSObject
{
}
- (BOOL) getBool:(NSString*)key withValue:(BOOL*)val;
- (BOOL) getInt:(NSString*)key withValue:(NSInteger*)val;
- (BOOL) getString:(NSString*)key withValue:(NSString*)val;
- (void) setBool:(NSString*)key withValue:(BOOL)val;
- (void) setInt:(NSString*)key withValue:(NSInteger)val;
- (void) setString:(NSString*)key withValue:(NSString*)val;
- (void) sync;
+(id) sharedInstance;
@end
+95
View File
@@ -0,0 +1,95 @@
//
// RobloxCachedFlags.mm
// RobloxMobile
//
// Created by Martin Robaszewski on 6/20/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import "RobloxCachedFlags.h"
@implementation RobloxCachedFlags
+ (id)sharedInstance
{
static dispatch_once_t rbxCachedFlagsPred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&rbxCachedFlagsPred, ^{ // Need to use GCD for thread-safe allocation
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-(id) init
{
if(self = [super init])
[self sync];
return self;
}
-(void) sync
{
[[NSUserDefaults standardUserDefaults] synchronize];
}
-(BOOL) getBool:(NSString *)key withValue:(BOOL*)val
{
NSObject *testObject = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if (testObject)
{
*val = [[NSUserDefaults standardUserDefaults] boolForKey:key];
return YES;
}
return NO;
}
-(BOOL) getInt:(NSString *)key withValue:(NSInteger*)val
{
NSObject *testObject = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if (testObject)
{
*val = [[NSUserDefaults standardUserDefaults] integerForKey:key];
return YES;
}
return NO;
}
-(BOOL) getString:(NSString *)key withValue:(NSString *)val
{
NSObject *testObject = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if (testObject)
{
val = [[NSUserDefaults standardUserDefaults] stringForKey:key];
return YES;
}
return NO;
}
- (void) setBool:(NSString*) key withValue:(BOOL)val
{
[[NSUserDefaults standardUserDefaults] setBool:val forKey:key];
[self sync];
}
- (void) setInt:(NSString*) key withValue:(NSInteger)val
{
[[NSUserDefaults standardUserDefaults] setInteger:val forKey:key];
[self sync];
}
- (void) setString:(NSString *)key withValue:(NSString *)val
{
[[NSUserDefaults standardUserDefaults] setObject:val forKey:key];
[self sync];
}
@end
+24
View File
@@ -0,0 +1,24 @@
//
// RobloxGoogleAnalytics.h
// RobloxMobile
//
// Created by Ganesh Agrawal on 11/13/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface RobloxGoogleAnalytics : NSObject
{}
+(void) startup;
+(void) setPageViewTracking:(NSString*)url;
+(void) setEventTracking:(NSString*)category withAction:(NSString*)action withValue:(NSInteger)value;
+(void) setEventTracking:(NSString*)category withAction:(NSString*)action withLabel:(NSString*)label withValue:(NSInteger)value;
+(void) setCustomVariableWithLabel:(NSString*)label withValue:(NSString*)value;
// debug counters
+(void) debugCountersPrint;
+(void) debugCounterIncrement:(NSString *)label;
@end
+177
View File
@@ -0,0 +1,177 @@
//
// RobloxGoogleAnalytics.m
// RobloxMobile
//
// Created by Ganesh Agrawal on 11/13/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import "RobloxGoogleAnalytics.h"
#ifndef RBX_INTERNAL
#include "iOSSettingsService.h"
#import <GAI.h>
#import <GAIFields.h>
#import <GAITracker.h>
#import <GAILogger.h>
#import <GAITrackedViewController.h>
#import <GAIDictionaryBuilder.h>
#import "RobloxInfo.h"
#import "RobloxWebUtility.h"
static const NSInteger kGANDispatchPeriodSec = 10;
#endif
@implementation RobloxGoogleAnalytics
BOOL initializeDone = NO;
+(void) startup
{
#ifndef RBX_INTERNAL
if(!initializeDone)
{
dispatch_async(dispatch_get_main_queue(), ^{
NSString* googleAccountString = [RobloxInfo getPlistStringFromKey:@"GoogleAnalyticsAccount"];
if(!googleAccountString)
{
NSLog(@"RobloxGoogleAnalytics cannot initialize due to missing account info");
return;
}
if([googleAccountString length] <= 0)
{
NSLog(@"RobloxGoogleAnalytics cannot initialize due to account info having length 0");
return;
}
[GAI sharedInstance].dispatchInterval = kGANDispatchPeriodSec;
[[GAI sharedInstance] trackerWithTrackingId:googleAccountString];
NSNumber* sampleRate = [RobloxInfo getPlistNumberFromKey:@"GoogleAnalyticsSampleRate"];
if(!sampleRate)
{
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
sampleRate = [NSNumber numberWithInt:iosSettings->GetValueiOSGoogleAnalyticsSampleRate()];
}
id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
[tracker set:kGAISampleRate value:[sampleRate stringValue]];
initializeDone = YES;
});
}
#endif
}
+(void) release
{
}
+(void) callBackPageTracking:(NSDictionary*) params {
[RobloxGoogleAnalytics setPageViewTracking:[params objectForKey:@"url"]];
}
+(void) setPageViewTracking:(NSString*)url
{
#ifndef RBX_INTERNAL
if(initializeDone) {
[[[GAI sharedInstance] defaultTracker] set:kGAIScreenName value:url];
[[[GAI sharedInstance] defaultTracker] send:[[GAIDictionaryBuilder createAppView] build]];
} else {
[self performSelector:@selector(callBackPageTracking:) withObject:[NSDictionary dictionaryWithObjectsAndKeys: url, @"url", nil] afterDelay:2];
}
#endif
}
+(void) callBackEventTracking:(NSDictionary*) params {
[RobloxGoogleAnalytics setEventTracking:[params objectForKey:@"category"]
withAction:[params objectForKey:@"action"]
withLabel:[params objectForKey:@"label"]
withValue:[[params objectForKey:@"value"] intValue]];
}
+(void) setEventTracking:(NSString*)category withAction:(NSString *)action withValue:(NSInteger)value
{
[self setEventTracking:category withAction:action withLabel:nil withValue:value];
}
+(void) setEventTracking:(NSString*)category withAction:(NSString*)action withLabel:(NSString*)label withValue:(NSInteger)value
{
#ifndef RBX_INTERNAL
if(initializeDone)
{
//NSLog(@"*** GA *** category:%@ action:%@ label:%@ value:%d", category, action, label, value);
[[[GAI sharedInstance] defaultTracker] send:[[GAIDictionaryBuilder createEventWithCategory:category action:action label:label value:[NSNumber numberWithInt:(int)value]] build]];
}
else
{
[self performSelector:@selector(callBackEventTracking:)
withObject:[NSDictionary dictionaryWithObjectsAndKeys: category, @"category", action, @"action", label, @"label", [NSString stringWithFormat:@"%lu", (unsigned long)value], @"value", nil]
afterDelay:2];
}
#endif
}
+(void) callbackCustomVariableTracking:(NSDictionary*) params {
[RobloxGoogleAnalytics setCustomVariableWithLabel:[params objectForKey:@"label"]
withValue:[params objectForKey:@"value"]];
}
+(void) setCustomVariableWithLabel:(NSString*)label withValue:(NSString*)value
{
#ifndef RBX_INTERNAL
if(initializeDone)
[[[GAI sharedInstance] defaultTracker] set:label value:value];
else
[self performSelector:@selector(callbackCustomVariableTracking:) withObject:[NSDictionary dictionaryWithObjectsAndKeys: label, @"label", value, @"value", nil] afterDelay:2];
#endif
}
+(void) debugCountersPrint
{
NSUserDefaults * stdUserDefaults = [NSUserDefaults standardUserDefaults];
[stdUserDefaults synchronize];
// get values
NSInteger appLaunch = [stdUserDefaults integerForKey:@"debug_appLaunch"];
NSInteger inApp = [stdUserDefaults integerForKey:@"debug_inApp"];
NSInteger inGame = [stdUserDefaults integerForKey:@"debug_inGame"];
NSInteger leaveGame = [stdUserDefaults integerForKey:@"debug_leaveGame"];
NSInteger tryGameJoin = [stdUserDefaults integerForKey:@"debug_tryGameJoin"];
NSInteger tryBackground = [stdUserDefaults integerForKey:@"debug_tryBackground"];
NSInteger tryForeground = [stdUserDefaults integerForKey:@"debug_tryForeground"];
NSInteger terminated = [stdUserDefaults integerForKey:@"debug_terminated"];
NSInteger inBackground = [stdUserDefaults integerForKey:@"debug_inBackground"];
// print them
NSLog(@"=== CRASH STATE DUMP ===");
NSLog(@"appLaunch %ld", (long)appLaunch);
NSLog(@"inApp %ld", (long)inApp);
NSLog(@"inGame %ld", (long)inGame);
NSLog(@"leaveGame %ld", (long)leaveGame);
NSLog(@"tryGameJoin %ld", (long)tryGameJoin);
NSLog(@"tryBackground %ld", (long)tryBackground);
NSLog(@"tryForeground %ld", (long)tryForeground);
NSLog(@"terminated %ld", (long)terminated);
NSLog(@"inBackground %ld", (long)inBackground);
}
+ (void) debugCounterIncrement:(NSString *)label
{
NSUserDefaults * stdUserDefaults = [NSUserDefaults standardUserDefaults];
[stdUserDefaults synchronize];
NSString * key = [NSString stringWithFormat:@"debug_%@", label];
NSInteger value = [stdUserDefaults integerForKey:key];
value++;
[stdUserDefaults setInteger:value forKey:key];
[stdUserDefaults synchronize];
NSLog(@"%@: %ld", key, (long)value);
}
@end
+48
View File
@@ -0,0 +1,48 @@
//
// RobloxInfo.h
// RobloxMobile
//
// Created by David York on 10/22/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface RobloxInfo : NSObject
{
}
+(BOOL) thisDeviceIsATablet;
+(BOOL) isTestSite;
+(BOOL) isDeviceOSVersionPreiOS8;
+(NSString*) getUserAgentString;
+(NSString*) deviceType;
+(NSString*) getBaseUrl;
+(void) setBaseUrl:(NSString*)url;
+(NSString*) getApiBaseUrl;
+(NSString*) getDomainString;
+(NSString*) getWWWBaseUrl;
+(NSString*) getSecureBaseUrl;
+(NSString*) getStoryboardName;
+(NSString*) deviceOSVersion;
+(NSString*) appVersion;
+(NSString*) friendlyDeviceName;
+(NSString*) searchUrl;
+(NSString*) getEnvironmentName:(bool)shortVersion;
+(NSString*) getPlistStringFromKey:(NSString*) key;
+(NSNumber*) getPlistNumberFromKey:(NSString*) key;
+(NSString *) gameGenres;
+(NSString*) getBaseUrlChangedNotification;
+(void) reportMaxMemoryUsedForPlaceID:(NSInteger)placeID;
+(void) setDefaultHTTPHeadersForRequest:(NSMutableURLRequest*)request;
@end
// Expose to C/C++
NSString* getBaseUrl();
NSString* getUserAgentString();
int getDeviceMemory();
bool hasMinMemory(int minMemory);
+587
View File
@@ -0,0 +1,587 @@
//
// RobloxInfo.m
// RobloxMobile
//
// Created by David York on 10/22/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import "RobloxInfo.h"
#import <UIKit/UIDevice.h>
#import <CoreFoundation/CoreFoundation.h>
#import "UserInfo.h"
#import "RobloxWebUtility.h"
#include "FastLog.h"
#include "util/Statistics.h"
#include "v8datamodel/FastLogSettings.h"
#include "iOSSettingsService.h"
#import "RobloxGoogleAnalytics.h"
#include "iOSSettingsService.h"
#include "v8datamodel/DataModel.h"
#include <sys/types.h>
#include <sys/sysctl.h>
#ifndef kCFCoreFoundationVersionNumber_iOS_8_0
#define kCFCoreFoundationVersionNumber_iOS_8_0 1129.15
#endif
DYNAMIC_FASTFLAGVARIABLE(RequireCuratedGames, false)
// Expose to C/C++
NSString* getBaseUrl() {
return [RobloxInfo getBaseUrl];
}
NSString* getUserAgentString() {
return [RobloxInfo getUserAgentString];
}
std::string getFriendlyDeviceName()
{
return [[RobloxInfo friendlyDeviceName] UTF8String];
}
std::string getDeviceOSVersion()
{
return [[RobloxInfo deviceOSVersion] UTF8String];
}
int getDeviceMemory()
{
int size = 0;
NSString *platform = [RobloxInfo deviceType];
if ([platform hasPrefix:@"iPod4"] || [platform hasPrefix:@"iPad1"] || [platform hasPrefix:@"iPhone2"]) // iPhone 3GS
{
size = 256;
}
else if ([platform hasPrefix:@"iPod5"] || [platform hasPrefix:@"iPad2"] || [platform hasPrefix:@"iPhone3"] || [platform hasPrefix:@"iPhone4"]) // iPhone 4/4S 512MB
{
size = 512;
}
else if ([platform hasPrefix:@"iPad3"] || [platform hasPrefix:@"iPad4"] || [platform hasPrefix:@"iPhone5"] || [platform hasPrefix:@"iPhone6"] || [platform hasPrefix:@"iPhone7"])
{
size = 1024;
}
return size;
}
bool hasMinMemory(int minMemory)
{
int size = getDeviceMemory();
if (size == 0)
{
return true; // unknown so we assume new device
}
else if (minMemory <= size)
{
return true;
}
return false;
}
@implementation RobloxInfo
static NSString * _machine = nil;
static NSString* baseUrl = nil;
static NSString* apiBaseUrl = nil;
static NSString* domainString = nil;
static NSString* wwwBaseUrl = nil;
static NSString* secureBaseUrl = nil;
static NSString* environmentLongName = nil;
static NSString* environmentShortName = nil;
static NSString* baseUrlChangedNotification = @"RBXBaseUrlChangedNotifier";
+(NSString*) getPlistStringFromKey:(NSString*) key
{
id idFromPlist = [[NSBundle mainBundle] objectForInfoDictionaryKey:key];
if (!idFromPlist)
{
return nil;
}
if(![idFromPlist isKindOfClass:[NSString class]])
{
return nil;
}
return (NSString*) idFromPlist;
}
+(NSNumber*) getPlistNumberFromKey:(NSString*) key
{
id idFromPlist = [[NSBundle mainBundle] objectForInfoDictionaryKey:key];
if (!idFromPlist)
{
return nil;
}
if(![idFromPlist isKindOfClass:[NSNumber class]])
{
return nil;
}
return (NSNumber*) idFromPlist;
}
+(NSString*)getDeviceType
{
NSString* model = [RobloxInfo deviceType];
if ([model rangeOfString:@"iPad"].location != NSNotFound) return @"iPad";
if ([model rangeOfString:@"iPhone"].location != NSNotFound) return @"iPhone";
if ([model rangeOfString:@"iPod"].location != NSNotFound) return @"iPod";
return @"Unknown";
}
// Get model number (-1 for unknown)
+(int) getDeviceModelNumber
{
NSString* model = [RobloxInfo deviceType];
int modelNumber = -1;
if ([RobloxInfo thisDeviceIsATablet])
{
NSRange range = [model rangeOfString:@"iPad"];
if (range.location != NSNotFound)
{
char chNum = [model characterAtIndex:(range.location+4)];
modelNumber = atoi(&chNum);
}
}
else
{
NSRange range;
range = [model rangeOfString:@"iPod"];
if (range.location != NSNotFound)
{
char chNum = [model characterAtIndex:(range.location+4)];
modelNumber = atoi(&chNum);
}
else
{
range = [model rangeOfString:@"iPhone"];
if (range.location != NSNotFound)
{
char chNum = [model characterAtIndex:(range.location+6)];
modelNumber = atoi(&chNum);
}
}
}
return modelNumber;
}
+(BOOL) thisDeviceIsATablet
{
return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
}
+(BOOL) isTestSite
{
NSString* url = getBaseUrl();
if ([url rangeOfString:@".pizzaboxer.fun"].location != NSNotFound)
return YES;
else
return NO;
}
+ (NSString *) deviceType
{
if (_machine == nil)
{
size_t size;
// Set 'oldp' parameter to NULL to get the size of the data
// returned so we can allocate appropriate amount of space
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
// Allocate the space to store name
char *name = (char*)malloc(size);
// Get the platform name
sysctlbyname("hw.machine", name, &size, NULL, 0);
// Place name into a string
_machine = [NSString stringWithUTF8String:name];
// Done with this
free(name);
}
return _machine;
}
+(NSString*) deviceOSVersion
{
return [[UIDevice currentDevice] systemVersion];
}
+(BOOL) isDeviceOSVersionPreiOS8
{
return NSFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_8_0;
}
+(NSString*) appVersion
{
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
}
static NSString* robloxDeviceName = nil;
+(NSString*) friendlyDeviceName
{
//This website has been immensely useful : http://www.everymac.com/ultimate-mac-lookup/
if (!robloxDeviceName)
{
//fetch the platform name
NSString *platform = [RobloxInfo deviceType];
//make a default name
robloxDeviceName = [NSString stringWithFormat:@"Unknown-%@",platform];
//try to find the real name
NSString* firstFour = [platform substringToIndex:(platform.length >= 4) ? 4 : platform.length]; //for faster parsing
if ([firstFour isEqualToString:@"iPho"])
{
if ([platform isEqualToString:@"iPhone1,1"]) robloxDeviceName = @"iPhone 1G";
else if ([platform isEqualToString:@"iPhone1,2"]) robloxDeviceName = @"iPhone 3G";
else if ([platform isEqualToString:@"iPhone2,1"]) robloxDeviceName = @"iPhone 3GS";
else if ([platform isEqualToString:@"iPhone3,1"]) robloxDeviceName = @"iPhone 4";
else if ([platform isEqualToString:@"iPhone3,2"]) robloxDeviceName = @"iPhone 4";
else if ([platform isEqualToString:@"iPhone3,3"]) robloxDeviceName = @"iPhone 4"; //(Verizon)
else if ([platform isEqualToString:@"iPhone4,1"]) robloxDeviceName = @"iPhone 4S";
else if ([platform isEqualToString:@"iPhone5,1"]) robloxDeviceName = @"iPhone 5"; //(GSM)
else if ([platform isEqualToString:@"iPhone5,2"]) robloxDeviceName = @"iPhone 5"; //(GSM+CDMA)
else if ([platform isEqualToString:@"iPhone5,3"]) robloxDeviceName = @"iPhone 5C"; //(GSM)
else if ([platform isEqualToString:@"iPhone5,4"]) robloxDeviceName = @"iPhone 5C"; //(Global)
else if ([platform isEqualToString:@"iPhone6,1"]) robloxDeviceName = @"iPhone 5S"; //(GSM)
else if ([platform isEqualToString:@"iPhone6,2"]) robloxDeviceName = @"iPhone 5S"; //(Global)
else if ([platform isEqualToString:@"iPhone7,1"]) robloxDeviceName = @"iPhone 6 Plus";
else if ([platform isEqualToString:@"iPhone7,2"]) robloxDeviceName = @"iPhone 6";
else if ([platform isEqualToString:@"iPhone8,1"]) robloxDeviceName = @"iPhone 6S";
else if ([platform isEqualToString:@"iPhone8,2"]) robloxDeviceName = @"iPhone 6S Plus";
}
else if ([firstFour isEqualToString:@"iPod"])
{
if ([platform isEqualToString:@"iPod1,1"]) robloxDeviceName = @"iPod Touch (1 Gen)";
else if ([platform isEqualToString:@"iPod2,1"]) robloxDeviceName = @"iPod Touch (2 Gen)";
else if ([platform isEqualToString:@"iPod3,1"]) robloxDeviceName = @"iPod Touch (3 Gen)";
else if ([platform isEqualToString:@"iPod4,1"]) robloxDeviceName = @"iPod Touch (4 Gen)";
else if ([platform isEqualToString:@"iPod5,1"]) robloxDeviceName = @"iPod Touch (5 Gen)";
else if ([platform isEqualToString:@"iPod7,1"]) robloxDeviceName = @"iPod Touch (6 Gen)";
}
else if ([firstFour isEqualToString:@"iPad"])
{
if ([platform isEqualToString:@"iPad1,1"]) robloxDeviceName = @"iPad 1";
else if ([platform isEqualToString:@"iPad2,1"]) robloxDeviceName = @"iPad 2"; //(WiFi)
else if ([platform isEqualToString:@"iPad2,2"]) robloxDeviceName = @"iPad 2"; //(GSM)
else if ([platform isEqualToString:@"iPad2,3"]) robloxDeviceName = @"iPad 2"; //(CDMA)
else if ([platform isEqualToString:@"iPad2,4"]) robloxDeviceName = @"iPad 2"; //(WiFi)
else if ([platform isEqualToString:@"iPad2,5"]) robloxDeviceName = @"iPad Mini 1"; //(WiFi)
else if ([platform isEqualToString:@"iPad2,6"]) robloxDeviceName = @"iPad Mini 1"; //(GSM)
else if ([platform isEqualToString:@"iPad2,7"]) robloxDeviceName = @"iPad Mini 1"; //(GSM+CDMA)
else if ([platform isEqualToString:@"iPad3,1"]) robloxDeviceName = @"iPad 3"; //(WiFi)
else if ([platform isEqualToString:@"iPad3,2"]) robloxDeviceName = @"iPad 3"; //(GSM+CDMA)
else if ([platform isEqualToString:@"iPad3,3"]) robloxDeviceName = @"iPad 3"; //(GSM)
else if ([platform isEqualToString:@"iPad3,4"]) robloxDeviceName = @"iPad 4"; //(WiFi)
else if ([platform isEqualToString:@"iPad3,5"]) robloxDeviceName = @"iPad 4"; //(GSM)
else if ([platform isEqualToString:@"iPad3,6"]) robloxDeviceName = @"iPad 4"; //(GSM+CDMA)
else if ([platform isEqualToString:@"iPad4,1"]) robloxDeviceName = @"iPad Air"; //(WiFi)
else if ([platform isEqualToString:@"iPad4,2"]) robloxDeviceName = @"iPad Air"; //(GSM+CDMA)
else if ([platform isEqualToString:@"iPad4,4"]) robloxDeviceName = @"iPad Mini 2"; //Retina (WiFi)
else if ([platform isEqualToString:@"iPad4,5"]) robloxDeviceName = @"iPad Mini 2"; //Retina (GSM+CDMA)
else if ([platform isEqualToString:@"iPad4,6"]) robloxDeviceName = @"iPad Mini 2"; //Retina (China)
else if ([platform isEqualToString:@"iPad4,7"]) robloxDeviceName = @"iPad Mini 3"; //(WiFi)
else if ([platform isEqualToString:@"iPad4,8"]) robloxDeviceName = @"iPad Mini 3"; //(GSM+CDMA)
else if ([platform isEqualToString:@"iPad4,9"]) robloxDeviceName = @"iPad Mini 3"; //(Wifi+China)
else if ([platform isEqualToString:@"iPad5,1"]) robloxDeviceName = @"iPad Mini 4"; //(Wifi Only)
else if ([platform isEqualToString:@"iPad5,2"]) robloxDeviceName = @"iPad Mini 4"; //(Wifi+Cellular)
else if ([platform isEqualToString:@"iPad5,3"]) robloxDeviceName = @"iPad Air 2"; //(WiFi)
else if ([platform isEqualToString:@"iPad5,4"]) robloxDeviceName = @"iPad Air 2"; //(GSM+CDMA)
}
else if ([firstFour isEqualToString:@"Watc"])
{
if ([platform isEqualToString:@"Watch1,1"]) robloxDeviceName = @"iWatch"; //38 mm
else if ([platform isEqualToString:@"Watch1,2"]) robloxDeviceName = @"iWatch"; //42 mm
}
else
{
if ([platform isEqualToString:@"i386"]) robloxDeviceName = @"Simulator 32 bit intel";
else if ([platform isEqualToString:@"x86_64"]) robloxDeviceName = @"Simulator 64 bit intel";
}
}
return robloxDeviceName;
#if 0
NSString* userFriendlyDeviceName = @"Unknown";
NSString* urlString;
urlString = [[RobloxInfo getBaseUrl] stringByAppendingString:@"mobileapi/friendly-device-name"];
NSURL *url = [NSURL URLWithString: urlString];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60*7];
[RobloxInfo setDefaultHTTPHeadersForRequest:theRequest];
[theRequest setHTTPMethod:@"GET"];
NSData *receivedData = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:NULL];
if (receivedData)
userFriendlyDeviceName = [[[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding] autorelease];
return userFriendlyDeviceName;
#endif
}
static NSString* userAgentString = nil;
+(NSString*) getUserAgentString
{
if (!userAgentString)
{
id userAgentName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RbxUserAgent"];
userAgentString = [NSString stringWithFormat:@"Mozilla/5.0 (%@; %@; CPU iPhone OS %@ like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B176 ROBLOX iOS App %@ %@Hybrid",
[UIDevice currentDevice].model,
[self deviceType],
[[UIDevice currentDevice] systemVersion],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
(userAgentName ? [(NSString*)userAgentName stringByAppendingString:@" "] : @"")];
}
return userAgentString;
}
+(void) setDefaultHTTPHeadersForRequest:(NSMutableURLRequest*)request
{
[request setValue:getUserAgentString() forHTTPHeaderField:@"User-Agent"];
BOOL requireCuratedGames = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"RbxRequireCuratedGames"] boolValue];
if(DFFlag::RequireCuratedGames && requireCuratedGames == YES)
{
[request setValue:@"true" forHTTPHeaderField:@"require-curated-games"];
}
}
+(NSString*) getBaseUrl
{
if (baseUrl == nil) {
if ([RobloxInfo thisDeviceIsATablet]) {
baseUrl = (NSString*)[[[NSBundle mainBundle] infoDictionary] objectForKey:@"RbxBaseUrl"];
} else {
baseUrl = (NSString*)[[[NSBundle mainBundle] infoDictionary] objectForKey:@"RbxBaseMobileUrl"];
}
if (![baseUrl hasSuffix:@"/"]) {
NSMutableString *newBaseUrl = [[NSMutableString alloc] initWithString:baseUrl];
[newBaseUrl appendString:@"/"];
baseUrl = newBaseUrl;
}
[RobloxInfo setBaseUrl:baseUrl];
}
return baseUrl;
}
+(NSString*) getStoryboardName
{
return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UIMainStoryboardFile"];
}
+(NSString*) getSecureBaseUrl
{
if (secureBaseUrl == nil)
{
NSString* url = [self getWWWBaseUrl];
if (![url isEqualToString:@""])
{
NSRange separator = [url rangeOfString:@":"];
NSString *Right = [url substringWithRange:NSMakeRange(separator.location , url.length - separator.location - 1)];
url = [NSString stringWithFormat:@"https%@", Right];
secureBaseUrl = url;
}
}
return secureBaseUrl;
}
+(NSString*) getApiBaseUrl
{
if (apiBaseUrl == nil)
{
NSString* url = getBaseUrl();
if(![url isEqualToString:@""])
{
NSRange separator = [url rangeOfString:@"."];
NSString *Right = [url substringWithRange:NSMakeRange(separator.location , url.length - separator.location - 1)];
url = [NSString stringWithFormat:@"https://api%@", Right];
apiBaseUrl = url;
}
}
return apiBaseUrl;
}
+(NSString*) getDomainString
{
if (domainString == nil)
{
NSString* url = getBaseUrl();
if(![url isEqualToString:@""])
{
url = [url stringByReplacingOccurrencesOfString:@"http://" withString:@""];
NSRange separator = [url rangeOfString:@"."];
url = [url substringWithRange:NSMakeRange(separator.location , url.length - separator.location - 1)];
url = [url stringByReplacingOccurrencesOfString:@"/" withString:@""];
domainString = url;
}
}
return domainString;
}
+(NSString*) getWWWBaseUrl
{
if (wwwBaseUrl == nil)
{
NSString* url = getBaseUrl();
NSRange separator = [url rangeOfString:@"."];
//keep the '/' at the end, do not truncate last character
NSString* right = [url substringWithRange:NSMakeRange(separator.location, url.length - separator.location)];
url = [NSString stringWithFormat:@"http://www%@", right];
wwwBaseUrl = url;
}
return wwwBaseUrl;
}
+(NSString*) getBaseUrlChangedNotification
{
return baseUrlChangedNotification;
}
+(void) setBaseUrl:(NSString*)url
{
baseUrl = url;
if (![url hasSuffix:@"/"]) {
url = [url stringByAppendingString:@"/"];
}
SetBaseURL([url UTF8String]);
[[NSNotificationCenter defaultCenter] postNotificationName:baseUrlChangedNotification object:self userInfo:nil];
[RobloxGoogleAnalytics startup];
//clear all the urls that have dependencies on the base url, they will be recreated the next time they are requested
apiBaseUrl = nil;
domainString = nil;
wwwBaseUrl = nil;
secureBaseUrl = nil;
environmentShortName = nil;
environmentLongName = nil;
}
+(NSString*) getEnvironmentName:(bool)shortVersion
{
if (environmentLongName == nil || environmentShortName == nil)
{
//parse out the environment name from the baseURL
NSString* url = [self getBaseUrl];
NSString* right = [url substringFromIndex:[url rangeOfString:@"."].location + 1];
NSString* environment = [right substringToIndex:[right rangeOfString:@"."].location];
if (environment)
{
environmentLongName = environment;
//NOTE - the function [NSString containsString:] is unsupported in versions of iOS before iOS 8
if ([environment isEqualToString:@"roblox"])
environmentShortName = @"PROD";
else if ([environment rangeOfString:@"sitetest"].location != NSNotFound)
environmentShortName = [NSString stringWithFormat:@"ST%@", [environment substringFromIndex:environment.length-1]];
else if ([environment rangeOfString:@"gametest"].location != NSNotFound)
environmentShortName = [NSString stringWithFormat:@"GT%@", [environment substringFromIndex:environment.length-1]];
else
environmentShortName = @"UNKNOWN";
}
else
{
environmentLongName = @"UNKNOWN";
environmentShortName = @"UNKNOWN";
}
}
//NSLog(@"Long Environment Name = %@", environmentLongName);
//NSLog(@"Short Environment Name = %@", environmentShortName);
if (shortVersion && environmentShortName)
return environmentShortName;
else if (!shortVersion && environmentLongName)
return environmentLongName;
//if all else fails
return @"UNKNOWN";
}
+(NSString*) searchUrl
{
const char* searchEndpointLog;
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
if ([RobloxInfo thisDeviceIsATablet] == YES)
searchEndpointLog = iosSettings->GetValueSearchEndpointIPad();
else
searchEndpointLog = iosSettings->GetValueSearchEndpointIPhone();
return [NSString stringWithUTF8String:searchEndpointLog];
}
+(NSString *) gameGenres
{
const char* genres;
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
genres = iosSettings->GetValueRBXGameGenres();
return [NSString stringWithUTF8String:genres];
}
+(void) reportMaxMemoryUsedForPlaceID:(NSInteger)placeID
{
NSLog(@"reportMaxMemoryUsedForPlaceID %ld", (long)placeID);
[[NSUserDefaults standardUserDefaults] synchronize];
NSInteger maxMemoryUsed = [[NSUserDefaults standardUserDefaults] integerForKey:@"MaxMemoryUsed"];
long megaBytes = maxMemoryUsed/(1024*1024);
NSLog(@"maxMemoryUsed: %ld megaBytes: %ld", (long)maxMemoryUsed, megaBytes);
if (megaBytes > 0)
{
NSString * placeIDString = [NSString stringWithFormat:@"%ld", (long)placeID];
NSString * deviceTypeString = [RobloxInfo deviceType];
NSLog(@"MAX MEMORY REPORT: deviceType:%@ place:%@ MB:%ld", deviceTypeString, placeIDString, megaBytes);
[RobloxGoogleAnalytics setEventTracking:@"MaxMemoryUsage"
withAction:deviceTypeString
withLabel:placeIDString
withValue:megaBytes];
}
}
@end
+20
View File
@@ -0,0 +1,20 @@
//
// RobloxJailbreakDetector.h
// RobloxMobile
//
// Created by Martin Robaszewski on 7/16/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#define JAILBREAK_IOS_CYDIA 0x00000001
#define JAILBREAK_IOS_BASH 0x00000002
#define JAILBREAK_IOS_VARLIBAPT 0x00000004
void jbDecodeString(const char * input, char * output);
int jbCheckAll();
int jbCheckCydia();
int jbCheckBash();
int jbCheckVarLibApt();
+225
View File
@@ -0,0 +1,225 @@
//
// RobloxJailbreakDetector.mm
// RobloxMobile
//
// Created by Martin Robaszewski on 7/16/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIApplication.h>
#import "RobloxJailbreakDetector.h"
#include "stdio.h"
// base64 taken from the internets
// http://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c
//
static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
BOOL isbase64(char c)
{
return c && strchr(table, c) != NULL;
}
inline char value(char c)
{
const char *p = strchr(table, c);
if(p) {
return p-table;
} else {
return 0;
}
}
int UnBase64(unsigned char *dest, const unsigned char *src, int srclen)
{
*dest = 0;
if(*src == 0)
{
return 0;
}
unsigned char *p = dest;
do
{
char a = value(src[0]);
char b = value(src[1]);
char c = value(src[2]);
char d = value(src[3]);
*p++ = (a << 2) | (b >> 4);
*p++ = (b << 4) | (c >> 2);
*p++ = (c << 6) | d;
if(!isbase64(src[1]))
{
p -= 2;
break;
}
else if(!isbase64(src[2]))
{
p -= 2;
break;
}
else if(!isbase64(src[3]))
{
p--;
break;
}
src += 4;
while(*src && (*src == 13 || *src == 10)) src++;
}
while(srclen-= 4);
*p = 0;
return p-dest;
}
//
//
//
#define JB_FEATURE_BITFLIP 1
#define JB_FEATURE_BASE64 1
void jbDecodeString(const unsigned char * input, unsigned char * output)
{
// 1. get len by reading thru aray until 0x00 is found
int len = 0;
while (input[len] != 0)
{
len++;
}
// temp buffer
unsigned char * temp = (unsigned char *)malloc((len+1) * sizeof(unsigned char));
memset(temp, 0, len+1);
memset(output, 0, len+1);
// 2. loop thru array
for (int a=0; a<len; a++)
{
unsigned char letter = input[a];
// flip bits if needed
#if JB_FEATURE_BITFLIP
letter = ~letter;
#endif
temp[a] = letter;
//printf("[%d] %02x %02x\n", a, input[a], temp[a]);
}
// 3. base64 decode if needed
#if JB_FEATURE_BASE64
UnBase64(output, temp, len);
#else
for (int a=0; a<len; a++)
{
output[a] = temp[a];
}
#endif
printf("decoded:%s\n", output);
free(temp);
}
int jbCheckAll()
{
int jbResult = 0;
jbResult += jbCheckCydia();
jbResult += jbCheckBash();
jbResult += jbCheckVarLibApt();
return jbResult;
}
// use str2c.pl script to generate obfuscated strings
//unsigned char bufferCydia[24] = { 0x2f,0x41,0x70,0x70,0x6c,0x69,0x63,0x61,0x74,0x69,0x6f,0x6e,0x73,0x2f,0x43,0x79,0x64,0x69,0x61,0x2e,0x61,0x70,0x70,0x00 }; //"/Applications/Cydia.app"
//unsigned char bufferCydiaBF[24] = { 0xd0,0xbe,0x8f,0x8f,0x93,0x96,0x9c,0x9e,0x8b,0x96,0x90,0x91,0x8c,0xd0,0xbc,0x86,0x9b,0x96,0x9e,0xd1,0x9e,0x8f,0x8f,0x00 }; //"/Applications/Cydia.app" BITFLIPPED
//unsigned char bufferCydia64[33] = { 0x4c,0x30,0x46,0x77,0x63,0x47,0x78,0x70,0x59,0x32,0x46,0x30,0x61,0x57,0x39,0x75,0x63,0x79,0x39,0x44,0x65,0x57,0x52,0x70,0x59,0x53,0x35,0x68,0x63,0x48,0x41,0x3d,0x00 }; //"/Applications/Cydia.app" BASE64
unsigned char bufferCydiaBF64[33] = { 0xb3,0xcf,0xb9,0x88,0x9c,0xb8,0x87,0x8f,0xa6,0xcd,0xb9,0xcf,0x9e,0xa8,0xc6,0x8a,0x9c,0x86,0xc6,0xbb,0x9a,0xa8,0xad,0x8f,0xa6,0xac,0xca,0x97,0x9c,0xb7,0xbe,0xc2,0x00 }; //"/Applications/Cydia.app" BITFLIPPED BASE64
unsigned char bufferCydiaUrlBF64[49] = { 0xa6,0xcc,0x93,0x94,0x9e,0xa8,0xba,0xc9,0xb3,0x86,0xc6,0x88,0xa6,0xa8,0xb1,0x8d,0xa6,0xa8,0x9b,0x93,0xb3,0xcd,0xb1,0x89,0x9d,0xac,0xca,0x93,0x9a,0xb8,0xb9,0x8b,0x9c,0xb8,0x87,0x93,0xb3,0x91,0xbd,0x97,0xa6,0xcd,0x8b,0x97,0xa5,0xcd,0xaa,0xc2,0x00 }; //"cydia://package/com.example.package" BITFLIPPED BASE64
int jbCheckCydia()
{
unsigned char bufferOutput[256];
memset(&bufferOutput, 0, 256);
jbDecodeString((const unsigned char *)&bufferCydiaBF64, (unsigned char *)&bufferOutput);
FILE *f = fopen((const char *)&bufferOutput, "r");
if (f != NULL)
{
fclose(f);
return JAILBREAK_IOS_CYDIA;
}
memset(&bufferOutput, 0, 256);
jbDecodeString((const unsigned char *)&bufferCydiaUrlBF64, (unsigned char *)&bufferOutput);
NSString * urlString = [NSString stringWithCString:(const char *)&bufferOutput encoding:[NSString defaultCStringEncoding]];
// test 2
NSURL* url = [NSURL URLWithString:urlString];
if ([[UIApplication sharedApplication] canOpenURL:url])
{
return JAILBREAK_IOS_CYDIA;
}
return 0;
}
unsigned char bufferBashBF64[13] = { 0xb3,0xcd,0xb5,0x8f,0x9d,0x96,0xc6,0x96,0xa6,0xa7,0xb1,0x90,0x00 }; //"/bin/bash" BITFLIPPED BASE64
int jbCheckBash()
{
unsigned char bufferOutput[256];
memset(&bufferOutput, 0, 256);
jbDecodeString((const unsigned char *)&bufferBashBF64, (unsigned char *)&bufferOutput);
FILE *f = fopen((const char *)&bufferOutput, "r");
if (f != NULL)
{
fclose(f);
return JAILBREAK_IOS_BASH;
}
return 0;
}
unsigned char bufferVarLibAptBF64[29] = { 0xb3,0xcc,0xbd,0x86,0x9e,0xa7,0xa5,0x97,0x9b,0xb8,0xaa,0x89,0x9b,0x92,0xb9,0x86,0xb3,0xcd,0x87,0x8f,0xa6,0x96,0xc6,0x97,0x9c,0xb7,0xae,0xc2,0x00 }; //"/private/var/lib/apt" BITFLIPPED BASE64
int jbCheckVarLibApt()
{
unsigned char bufferOutput[256];
memset(&bufferOutput, 0, 256);
jbDecodeString((const unsigned char *)&bufferVarLibAptBF64, (unsigned char *)&bufferOutput);
FILE *f = fopen((const char *)&bufferOutput, "r");
if (f != NULL)
{
fclose(f);
return JAILBREAK_IOS_VARLIBAPT;
}
return 0;
}
/*
run unsigned code
has Cydia installed
has jailbreak files
full r/w access to the whole filesystem
some system files will have been modified (content and so sha1 doesn't match with original files)
stuck to specific version (jailbreakable version)
*/
/*
- (void)applicationWillTerminate:(UIApplication *)application {
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent: @"test"];
int result = execlp([path UTF8String], [path UTF8String], NULL);
// if we got here, then the attempt to run the external process failed, so make a note of this:
[[NSUserDefaults standardUserDefaults] setValue: [NSNumber numberWithBool: NO] forKey: @"is_jailbroken"];
NSLog(@"test returns %d", result);
}
*/
+68
View File
@@ -0,0 +1,68 @@
//
// RobloxMemoryManager.h
// RobloxMobile
//
// Created by Martin Robaszewski on 5/15/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RobloxWebUtility.h"
#import "iOSSettingsService.h"
vm_size_t usedMemory(void);
vm_size_t freeMemory(void);
void print_free_memory();
void debug_stealMemory(size_t bytes);
@interface RobloxMemoryManager : NSObject
{
// memory bouncer - iOSClientSettings
BOOL isMemoryBouncerActive;
int memoryBouncerEnforceRate;
int memoryBouncerThresholdKB;
int memoryBouncerLimitMB;
int memoryBouncerBlockSizeKB;
BOOL bMemBouncerCrashed;
// memory bouncer
void * balloonMemoryBlock;
std::vector<void *> memBlocks;
NSTimer * memoryBouncerTimer;
int stopCount;
// free memory checking
NSTimer * freeMemoryCheckerTimer;
BOOL isFreeMemoryCheckerActive;
int freeMemoryCheckerRate; // in seconds
int freeMemoryCheckerTheshold; // in KiloBytes
// max memory tracker
NSTimer * maxMemoryTrackerTimer;
int maxMemoryTrackerRate;
NSInteger maxMemoryUsed;
}
+ (id)sharedInstance;
// memory bouncer functions
-(void) startMemoryBouncer;
-(BOOL) stopMemoryBouncer:(BOOL)bForce;
-(void) balloonMemory;
-(void) popBalloon;
-(void) bounceFreeMemory:(NSTimer *)timer;
-(void) memBouncerCrashed;
// free memory checking
-(void) startFreeMemoryChecker;
-(void) stopFreeMemoryChecker;
-(void) checkFreeMemory:(NSTimer *)timer;
-(void) logMemUsage:(NSTimer *)timer;
// max memory reporting
-(void) startMaxMemoryTracker;
-(void) checkMaxMemory:(NSTimer *)timer;
-(void) stopMaxMemoryTracker;
@end
+487
View File
@@ -0,0 +1,487 @@
//
// RobloxMemoryManager.m
// RobloxMobile
//
// Created by Martin Robaszewski on 5/15/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import "RobloxMemoryManager.h"
#import "RobloxGoogleAnalytics.h"
#import "PlaceLauncher.h"
#import "RobloxInfo.h"
#include "ObjectiveCUtilities.h"
#include <time.h>
#include <sys/time.h>
#ifdef __MACH__
#include <mach/clock.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#endif
// memory functions from stackoverflow.com
vm_size_t usedMemory(void)
{
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
return (kerr == KERN_SUCCESS) ? info.resident_size : 0; // size in bytes
}
vm_size_t freeMemory(void)
{
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t pagesize;
vm_statistics_data_t vm_stat;
host_page_size(host_port, &pagesize);
(void) host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
return vm_stat.free_count * pagesize;
}
void print_free_memory()
{
mach_port_t host_port;
mach_msg_type_number_t host_size;
vm_size_t pagesize;
host_port = mach_host_self();
host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
host_page_size(host_port, &pagesize);
vm_statistics_data_t vm_stat;
if (host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size) != KERN_SUCCESS)
NSLog(@"Failed to fetch vm statistics");
/* Stats in bytes */
natural_t mem_used = (vm_stat.active_count +
vm_stat.inactive_count +
vm_stat.wire_count) * pagesize;
natural_t mem_free = vm_stat.free_count * pagesize;
natural_t mem_total = mem_used + mem_free;
NSLog(@"used: %u free: %u total: %u", mem_used, mem_free, mem_total);
}
static std::vector<void *> debug_memBlocks;
void debug_stealMemory(size_t bytes)
{
NSLog(@"stealMemory(%ld)", bytes);
void * memBlock = 0;
print_free_memory();
memBlock = malloc(bytes);
if (memBlock != 0)
{
// write to the block to make it real otherwise it wont really be allocated
int value = 0;
for (long a=0; a<bytes; a += 1024)
{
((unsigned char *)memBlock)[a] = value;
value++;
}
debug_memBlocks.push_back(memBlock);
}
else
{
NSLog(@"failed!");
}
print_free_memory();
}
@implementation RobloxMemoryManager
+ (id)sharedInstance
{
static dispatch_once_t robloxMemoryManagerPred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&robloxMemoryManagerPred, ^{ // Need to use GCD for thread-safe allocation
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
//
// MEMORY BOUNCER FUNCTIONS
//
-(void) startMemoryBouncer
{
if (isMemoryBouncerActive)
{
return;
}
stopCount = 0;
NSLog(@"RMM::startMB");
// memory bouncer
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
isMemoryBouncerActive = iosSettings->GetValueMemoryBouncerActive();
memoryBouncerEnforceRate = iosSettings->GetValueMemoryBouncerEnforceRateMilliSeconds();
memoryBouncerThresholdKB = iosSettings->GetValueMemoryBouncerThresholdKiloBytes();
memoryBouncerLimitMB = iosSettings->GetValueMemoryBouncerLimitMegaBytes();
memoryBouncerBlockSizeKB = iosSettings->GetValueMemoryBouncerBlockSizeKB();
int memBouncerDelayCountNew = iosSettings->GetValueMemoryBouncerDelayCount();
if (bMemBouncerCrashed)
{
// set delay counter...
[[NSUserDefaults standardUserDefaults] setInteger:memBouncerDelayCountNew forKey:@"MemBouncerDelayCount"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
// check if there is a delay
int memBouncerDelayCount = [[NSUserDefaults standardUserDefaults] integerForKey:@"MemBouncerDelayCount"];
if (memBouncerDelayCount > 0)
{
NSLog(@"MB - memBouncerDelayCount %d", memBouncerDelayCount);
isMemoryBouncerActive = false;
memBouncerDelayCount--;
[[NSUserDefaults standardUserDefaults] setInteger:memBouncerDelayCount forKey:@"MemBouncerDelayCount"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
NSString *platform = [RobloxInfo deviceType];
// use MemoryBouncerLimitMegaBytesLow value for older devices
if ([platform hasPrefix:@"iPod4"] ||
[platform hasPrefix:@"iPad1"] ||
[platform hasPrefix:@"iPhone2"] // iPhone 3GS
)
{
memoryBouncerLimitMB = iosSettings->GetValueMemoryBouncerLimitMegaBytesForLowMemDevices();
}
// logging
long freeMem = freeMemory();
long usedMem = usedMemory();
long totalMemory = freeMem + usedMem;
NSLog(@"MB isMBActive:%d MBEnforceRate:%d MBThresholdKB:%d MBLimitMB:%d totalM:%ld platform:%@", isMemoryBouncerActive, memoryBouncerEnforceRate, memoryBouncerThresholdKB, memoryBouncerLimitMB, totalMemory, platform );
NSLog(@"MB free mem: %ld", freeMem);
NSLog(@"MB used mem: %ld", usedMem);
NSLog(@"MB total : %ld", totalMemory);
print_free_memory();
if (isMemoryBouncerActive)
{
[[NSUserDefaults standardUserDefaults] setObject:@"Bouncer" forKey:@"RobloxMemMgrState"];
[[NSUserDefaults standardUserDefaults] synchronize];
double checkTime = (double)memoryBouncerEnforceRate/1000.0;
memoryBouncerTimer = [NSTimer scheduledTimerWithTimeInterval:checkTime
target:self
selector:@selector(bounceFreeMemory:)
userInfo:nil
repeats:YES];
[self balloonMemory];
}
}
-(BOOL) stopMemoryBouncer:(BOOL)bForce
{
BOOL bStopped = FALSE;
stopCount++;
if (memoryBouncerTimer != nil)
{
if (bForce) // forcing by bouncer
{
[memoryBouncerTimer invalidate];
memoryBouncerTimer = nil;
bStopped = TRUE;
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"RobloxMemMgrState"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
[self popBalloon];
NSLog(@"RMM::stopMB stopCount:%d stopped:%d", stopCount, bStopped);
return bStopped;
}
// use the memory to force it into reality
void makeMemoryBlockDirty(void * memBlock, long memSize)
{
int value = 0;
for (long a=0; a<memSize; a += 1024)
{
((unsigned char *)memBlock)[a] = value;
value++;
}
}
-(void) balloonMemory
{
[self popBalloon];
long memorySize = freeMemory();
long mbSize = (memorySize/(1024*1024));
NSLog(@"RMM::balloonMB %ld %ldMB mbbsKB %d", memorySize, mbSize, memoryBouncerBlockSizeKB);
if (mbSize >= memoryBouncerLimitMB) // we are done
{
NSLog(@"mbSize >= MBLimitMB --- STOPPING");
[self stopMemoryBouncer:TRUE];
return;
}
if (memoryBouncerBlockSizeKB == 0)
{
balloonMemoryBlock = malloc(memorySize);
if (balloonMemoryBlock)
{
makeMemoryBlockDirty(balloonMemoryBlock, memorySize);
}
else
{
[self stopMemoryBouncer:TRUE];
}
}
else
{
bool bRun = true;
long totalUsedSize = 0;
do
{
long memBlockSize = memoryBouncerBlockSizeKB * 1024;
long usedSize = 0;
if (memorySize > memBlockSize)
{
usedSize = memBlockSize;
}
else
{
usedSize = memorySize;
bRun = false;
}
memorySize -= usedSize;
balloonMemoryBlock = malloc(usedSize);
//NSLog(@"usedSize: %ld totalUsedSize %ld remaining: %ld", usedSize, totalUsedSize, memorySize);
if (balloonMemoryBlock)
{
makeMemoryBlockDirty(balloonMemoryBlock, usedSize);
memBlocks.push_back(balloonMemoryBlock);
totalUsedSize += usedSize;
}
else
{
//NSLog(@"--- malloc() failed");
[self stopMemoryBouncer:TRUE];
bRun = false;
}
}
while (bRun);
balloonMemoryBlock = 0; // clear it
}
}
-(void) popBalloon
{
if (memoryBouncerBlockSizeKB == 0)
{
if (balloonMemoryBlock)
{
free(balloonMemoryBlock);
balloonMemoryBlock = 0;
}
}
else
{
while (int size = memBlocks.size())
{
//NSLog(@"size %d - freeing index %d", size, (size-1));
free(memBlocks[size-1]);
memBlocks.pop_back();
}
}
}
-(void) bounceFreeMemory:(NSTimer *)timer;
{
int kiloBytesFree = freeMemory()/1024.0f;
NSLog(@"RMM::bounceFM - %d", kiloBytesFree);
if (kiloBytesFree > memoryBouncerThresholdKB)
{
[self balloonMemory];
}
}
-(void) memBouncerCrashed
{
NSLog(@"RMM::bouncerCrashed");
bMemBouncerCrashed = true;
}
//
// FREE MEMORY CHECKER FUNCTIONS
//
-(void) startFreeMemoryChecker
{
NSLog(@"RMM::startFMC");
print_free_memory();
[self stopMemoryBouncer:TRUE]; // force stop bouncer
// start timer (if enabled) to detect low memory
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
isFreeMemoryCheckerActive = iosSettings->GetValueFreeMemoryCheckerActive();
freeMemoryCheckerRate = iosSettings->GetValueFreeMemoryCheckerRateMilliSeconds();
freeMemoryCheckerTheshold = iosSettings->GetValueFreeMemoryCheckerThresholdKiloBytes();
//freeMemoryCheckerTheshold = 20000; // override test
if (isFreeMemoryCheckerActive)
{
NSLog(@"STARTING FMC - rate %dsec threshold %dkB", freeMemoryCheckerRate, freeMemoryCheckerTheshold);
[[NSUserDefaults standardUserDefaults] setObject:@"Checker" forKey:@"RobloxMemMgrState"];
[[NSUserDefaults standardUserDefaults] synchronize];
double checkTime = (double)freeMemoryCheckerRate/1000.0;
freeMemoryCheckerTimer = [NSTimer scheduledTimerWithTimeInterval:checkTime
target:self
selector:@selector(checkFreeMemory:)
userInfo:nil
repeats:YES];
}
else
{
NSLog(@"NOT STARTING FMC - rate %dsec threshold %dkB", freeMemoryCheckerRate, freeMemoryCheckerTheshold);
}
}
-(void) stopFreeMemoryChecker
{
NSLog(@"RMM::stopFMC");
if (freeMemoryCheckerTimer != nil)
{
[freeMemoryCheckerTimer invalidate];
freeMemoryCheckerTimer = nil;
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"RobloxMemMgrState"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
-(void) checkFreeMemory:(NSTimer *)timer;
{
int kiloBytesFree = freeMemory()/1024.0f;
if (kiloBytesFree < freeMemoryCheckerTheshold)
{
NSLog(@"RMM::checkFreeMemory %dkB passed threshold %dkB", kiloBytesFree, freeMemoryCheckerTheshold);
NSString* freeMemoryType = [NSString stringWithFormat:@"%d_%d", freeMemoryCheckerRate, freeMemoryCheckerTheshold];
[RobloxGoogleAnalytics setEventTracking:@"PlayErrors" withAction:@"OutOfMemory_EarlyExit" withLabel:freeMemoryType withValue:kiloBytesFree];
[[PlaceLauncher sharedInstance] applicationDidReceiveMemoryWarning];
}
else
{
[self logMemUsage:timer];
}
}
-(void) logMemUsage:(NSTimer *)timer;
{
// compute memory usage and log if different by >= 100k
static long prevMemUsage = 0;
long curMemUsage = usedMemory();
long memUsageDiff = curMemUsage - prevMemUsage;
if (memUsageDiff > 100000 || memUsageDiff < -100000)
{
prevMemUsage = curMemUsage;
if (timer != nil)
{
NSLog(@"RMM::logMemUsage BEAT M_used %7.1f (%+5.0f), free %7.1f kb", curMemUsage/1000.0f, memUsageDiff/1000.0f, freeMemory()/1000.0f);
}
else
{
NSLog(@"RMM::logMemUsage **** M_used %7.1f (%+5.0f), free %7.1f kb", curMemUsage/1000.0f, memUsageDiff/1000.0f, freeMemory()/1000.0f);
}
}
}
//
// MAX MEMORY TRACKER
//
-(void) startMaxMemoryTracker
{
NSLog(@"RMM::startMMT");
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
maxMemoryTrackerRate = iosSettings->GetValueMaxMemoryReporterRateMilliSeconds();
maxMemoryUsed = usedMemory();
if (maxMemoryTrackerRate > 0)
{
NSLog(@"STARTING MMT - rate %d", maxMemoryTrackerRate);
[[NSUserDefaults standardUserDefaults] setInteger:maxMemoryUsed forKey:@"MaxMemoryUsed"];
[[NSUserDefaults standardUserDefaults] synchronize];
double checkTime = (double)maxMemoryTrackerRate/1000.0;
maxMemoryTrackerTimer = [NSTimer scheduledTimerWithTimeInterval:checkTime
target:self
selector:@selector(checkMaxMemory:)
userInfo:nil
repeats:YES];
}
else
{
NSLog(@"NOT STARTING MMT - rate %d", maxMemoryTrackerRate);
}
}
-(void) checkMaxMemory:(NSTimer *)timer
{
long currMemUsage = usedMemory();
if (currMemUsage > maxMemoryUsed)
{
maxMemoryUsed = currMemUsage;
[[NSUserDefaults standardUserDefaults] setInteger:maxMemoryUsed forKey:@"MaxMemoryUsed"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
-(void) stopMaxMemoryTracker
{
NSLog(@"RMM::stopMMT");
if (maxMemoryTrackerTimer != nil)
{
[maxMemoryTrackerTimer invalidate];
maxMemoryTrackerTimer = nil;
}
}
@end
+4
View File
@@ -0,0 +1,4 @@
// RobloxPlayer.cpp : Defines the entry point for the console application.
//
// NOTE: Contents of this file have been gutted and moved to various other files and classes. Let's remove it from source control soon -- TP
+458
View File
@@ -0,0 +1,458 @@
#include "RobloxView.h"
#include "CoreFoundation/CoreFoundation.h"
#include "GfxBase/ViewBase.h"
#include "v8datamodel/BaseRenderJob.h"
#include "v8datamodel/workspace.h"
#include "v8datamodel/camera.h"
#include "v8datamodel/game.h"
#include "FunctionMarshaller.h"
#include "Util/StandardOut.h"
#include "Util/FileSystem.h"
#include "rbx/Tasks/Coordinator.h"
#include "Util/IMetric.h"
#include "Util/Object.h"
#include "GfxBase/RenderSettings.h"
#include "GfxBase/FrameRateManager.h"
#include "v8datamodel/UserController.h"
#include "Util/Statistics.h"
#include "v8datamodel/ContentProvider.h"
#include "script/ScriptContext.h"
#include "v8xml/Serializer.h"
#include "rbx/CEvent.h"
#include "GameVerbs.h"
#include "Network/Players.h"
#include "../ClientBase/RenderSettingsItem.h"
#include "RbxInputView.h"
#include "rbx/SystemUtil.h"
#include <boost/iostreams/copy.hpp>
#include "Roblox.h"
#include "V8DataModel/GameBasicSettings.h"
#include "FastLog.h"
LOGGROUP(RenderBreakdown)
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
FASTFLAG(RenderLowLatencyLoop)
// This job calls ViewBase::render(), which needs to be done exclusive to the DataModel.
// This is why it has the RBX::DataModelJob::Render enum, which prevents concurrent writes to DataModel.
// It also needs to run in the view's thread for OpenGL
class RobloxView::RenderJob : public RBX::BaseRenderJob
, public RBX::IMetric
{
RBX::FunctionMarshaller* marshaller;
weak_ptr<RBX::DataModel> dataModel;
RBX::ViewBase* view;
RBX::CEvent renderEvent;
RBX::CEvent prepareBeginEvent;
RBX::CEvent prepareEndEvent;
volatile int stopped;
public:
RenderJob(RBX::ViewBase* view, RBX::FunctionMarshaller* marshaller, shared_ptr<RBX::DataModel> dataModel)
: RBX::BaseRenderJob(CRenderSettingsItem::singleton().getMinFrameRate() ,CRenderSettingsItem::singleton().getMaxFrameRate(), dataModel)
, view(view)
, dataModel(dataModel)
, marshaller(marshaller)
, renderEvent(false)
, prepareBeginEvent(false)
, prepareEndEvent(false)
, stopped(0)
{
cyclicExecutive = true;
}
RBX::Time::Interval sleepTime(const Stats& stats)
{
if(isAwake)
return computeStandardSleepTime(stats, CRenderSettingsItem::singleton().getMaxFrameRate());
else
return RBX::Time::Interval::max();
}
void stop()
{
stopped = 1;
}
static void scheduleRender(weak_ptr<RenderJob> selfWeak, ViewBase* view, double timeJobStart)
{
shared_ptr<RenderJob> self = selfWeak.lock();
if (!self) return;
self->prepareBeginEvent.Wait();
view->renderPrepare(self.get());
self->prepareEndEvent.Set();
view->renderPerform(timeJobStart);
self->wake();
}
static void scheduleRenderPrepare(RenderJob* self, ViewBase* view)
{
if (self->stopped != 0)
return;
view->renderPrepare(self);
}
static void scheduleRenderPerform(RenderJob* self, ViewBase* view, double timeJobStart)
{
if( !self->dataModel.lock() )
return;
if ( self->stopped != 0 )
return;
if(!view)
return;
view->renderPerform(timeJobStart);
self->wake();
}
virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
{
shared_ptr<RBX::DataModel> dm(dataModel.lock());
if (!dm || stopped)
return RBX::TaskScheduler::Done;
// Initially a view does not have a data model; it gets one during bindWorkspace.
// If bindWorkspace is launched asynchronously, there is a possibility of a race -
// render job might start before bindWorkspace gets a chance to run.
// It should be safe to skip the render job in this case.
if (!view->getDataModel())
return RBX::TaskScheduler::Stepped;
double timeJobStart = Time::nowFastSec();
if (FFlag::RenderLowLatencyLoop)
{
RBX::DataModel::scoped_write_request request(dm.get());
const double renderDelta = timeSinceLastRender().seconds();
lastRenderTime = RBX::Time::now<RBX::Time::Fast>();
isAwake = false;
marshaller->Submit(boost::bind(&scheduleRender, weak_from(this), view, timeJobStart));
view->updateVR();
dm->renderStep(renderDelta);
prepareBeginEvent.Set();
prepareEndEvent.Wait();
}
else
{
try
{
{
RBX::DataModel::scoped_write_request request(dm.get());
const double renderDelta = timeSinceLastRender().seconds();
lastRenderTime = RBX::Time::now<RBX::Time::Fast>();
view->updateVR();
dm->renderStep(renderDelta);
isAwake = false;
FASTLOG(FLog::RenderBreakdown, "Trigger renderPrepare");
marshaller->Execute(boost::bind(&scheduleRenderPrepare, this, view), &renderEvent);
FASTLOG(FLog::RenderBreakdown, "Finished renderPrepare");
}
{
FASTLOG(FLog::RenderBreakdown, "Trigger renderPerform");
marshaller->Submit(boost::bind(&scheduleRenderPerform, this, view, timeJobStart));
FASTLOG(FLog::RenderBreakdown, "Finished renderPerform");
}
}
catch (RBX::base_exception& e)
{
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e);
}
}
return RBX::TaskScheduler::Stepped;
}
void abortRender()
{
renderEvent.Set();
}
// IMetric
/*override*/ double getMetricValue(const std::string& metric) const
{
RBX::FrameRateManager* frm = view ? view->getFrameRateManager() : 0;
if (metric == "Render FPS")
return averageStepsPerSecond();
if (metric == "Render Duty")
return averageDutyCycle();
if (metric == "Render Job Time")
return averageStepTime();
if (metric == "Render Nominal FPS")
return frm ? 1000.0 / frm->GetRenderTimeAverage() : 0.0;
if (metric == "Delta Between Renders")
return view->getMetricValue(metric);
if (metric == "Total Render")
return view->getMetricValue(metric);
if (metric == "Present Time")
return view->getMetricValue(metric);
if (metric == "GPU Delay")
return view->getMetricValue(metric);
if (metric == "Render Prepare")
return view->getMetricValue(metric);
if (metric == "Video Memory MB")
return RBX::SystemUtil::getVideoMemory() / 1e6;
return 0.0;
}
/*override*/ std::string getMetric(const std::string& metric) const
{
if (! view )
return "No View";
if (metric == "Graphics Mode")
return "";
RBX::FrameRateManager* frm = view ? view->getFrameRateManager() : 0;
if (metric == "FRM")
return (frm && frm->IsBlockCullingEnabled()) ? "On" : "Off";
if (metric == "Anti-Aliasing")
return (frm && frm->getAntialiasingMode() == RBX::CRenderSettings::AntialiasingOn) ? "On" : "Off";
RBXASSERT(0);
return "";
}
};
void RobloxView::requestStopRenderingForBackgroundMode()
{
if (renderJob)
{
renderJob->abortRender();
boost::function<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, marshaller);
RBX::TaskScheduler::singleton().removeBlocking(renderJob, callback);
}
// RenderJob is sure to be completed at this point, since removeBlocking returned - but it might have marshalled
// renderPerform asynchronously before exiting, which means that we might still have a callback that uses this view
// in the marshaller queue.
// This makes sure that all pending marshalled events are processed to avoid a use after free.
marshaller->ProcessMessages();
// All render processing is complete; it's safe to reset job pointers now
renderJob.reset();
}
void RobloxView::requestResumeRendering()
{
renderJob = shared_ptr<RenderJob>(new RenderJob(view.get(), marshaller, game->getDataModel()));
RBX::TaskScheduler::singleton().add(renderJob);
}
std::string macBundlePath()
{
char path[1024];
CFBundleRef mainBundle = CFBundleGetMainBundle();
assert(mainBundle);
CFURLRef mainBundleURL = CFBundleCopyBundleURL(mainBundle);
assert(mainBundleURL);
CFStringRef cfStringRef = CFURLCopyFileSystemPath( mainBundleURL, kCFURLPOSIXPathStyle);
assert(cfStringRef);
CFStringGetCString(cfStringRef, path, 1024, kCFStringEncodingASCII);
CFRelease(mainBundleURL);
CFRelease(cfStringRef);
return std::string(path);
}
static RBX::ViewBase* createGameWindow(RobloxView* view, void *wnd, unsigned int width, unsigned int height)
{
// static initialization:
static boost::once_flag flag = BOOST_ONCE_INIT;
boost::call_once(&RBX::ViewBase::InitPluginModules, flag);
RBX::OSContext context;
context.hWnd = wnd;
context.width = width;
context.height = height;
CRenderSettingsItem& settings = CRenderSettingsItem::singleton();
RBX::CRenderSettings::GraphicsMode mode = RBX::CRenderSettings::OpenGL;
RBX::ViewBase* rbxView = RBX::ViewBase::CreateView(mode, &context, &settings);
rbxView->initResources();
return rbxView;
}
RobloxView::RobloxView(void* wnd, unsigned int width, unsigned int height)
:view(createGameWindow(this, wnd, width, height))
,marshaller(RBX::FunctionMarshaller::GetWindow())
{
}
void RobloxView::completeViewPrep(shared_ptr<RBX::Game> game)
{
this->game = game;
placeIDChangeConnection = game->getDataModel()->propertyChangedSignal.connect( boost::bind(&RobloxView::onPlaceIDChanged, this, _1) );
shared_ptr<DataModel> dataModelToSubmitOn = game->getDataModel();
{
RBX::DataModel::LegacyLock lock(dataModelToSubmitOn.get(), RBX::DataModelJob::Write);
if( RBX::UserInputService* userInputService = RBX::ServiceProvider::create<RBX::UserInputService>(dataModelToSubmitOn.get()) )
{
userInputService->setTouchEnabled(true);
userInputService->setAccelerometerEnabled(true);
if ([RbxInputView isGyroscopeAvailable])
{
userInputService->setGyroscopeEnabled(true);
}
}
}
bindWorkspace(view, game->getDataModel());
// complete render jobs setup
renderJob = shared_ptr<RenderJob>(new RenderJob(view.get(), marshaller, game->getDataModel()));
defineConcurrencyRules();
// Important! only schedule view and render jobs after concurrency rules are defined
RBX::TaskScheduler::singleton().add(renderJob);
leaveGameVerb.reset(new LeaveGameVerb(this, game->getDataModel().get()));
}
void RobloxView::newGameDidStart()
{
dispatch_async( dispatch_get_main_queue(), ^{
// now we can start rendering again
requestResumeRendering();
});
}
void RobloxView::onPlaceIDChanged(const RBX::Reflection::PropertyDescriptor* desc)
{
#if !RBX_PLATFORM_IOS
bool placeIDChanged = desc->name=="PlaceId";
if(placeIDChanged && dataModel->getPlaceID() > 0)
Roblox::addBreakPadKeyValue("Place0", dataModel->getPlaceID());
#endif
}
void RobloxView::defineConcurrencyRules()
{
RBXASSERT(renderJob);
{
// Force viewUpdateJob and renderJob to happen serially
boost::shared_ptr<RBX::Tasks::Coordinator> sequence(new RBX::Tasks::ExclusiveSequence());
renderJob->addCoordinator(sequence);
}
if (CRenderSettingsItem::singleton().isSynchronizedWithPhysics)
{
// Force rendering and physics to happen in lock-step
sequence.reset(new RBX::Tasks::Sequence());
renderJob->addCoordinator(sequence);
game->getDataModel()->create<RBX::RunService>()->getPhysicsJob()->addCoordinator(sequence);
}
}
RobloxView::~RobloxView(void)
{
if (sequence)
{
if (RBX::RunService* rs = game->getDataModel()->find<RBX::RunService>())
rs->getPhysicsJob()->removeCoordinator(sequence);
}
if (renderJob)
{
renderJob->abortRender();
boost::function<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, marshaller);
RBX::TaskScheduler::singleton().removeBlocking(renderJob, callback);
}
// RenderJob is sure to be completed at this point, since removeBlocking returned - but it might have marshalled
// renderPerform asynchronously before exiting, which means that we might still have a callback that uses this view
// in the marshaller queue.
// This makes sure that all pending marshalled events are processed to avoid a use after free.
marshaller->ProcessMessages();
// Set the flag on data model shutting down, this will prevent further network packets to be processed
if (boost::shared_ptr<RBX::DataModel> dataModel = game->getDataModel())
{
// give scripts a deadline to finish
if (FLog::PlayerShutdownLuaTimeoutSeconds > 0)
if (ScriptContext* scriptContext = game->getDataModel()->find<ScriptContext>())
scriptContext->setTimeout(FLog::PlayerShutdownLuaTimeoutSeconds);
dataModel->setIsShuttingDown(true);
}
{
RBX::DataModel::LegacyLock lock(game->getDataModel().get(), RBX::DataModelJob::Write);
RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(game->getDataModel().get());
service->setHardwareDevice(NULL);
view->bindWorkspace(boost::shared_ptr<RBX::DataModel>());
}
RBX::FunctionMarshaller::ReleaseWindow(marshaller);
// First destroy the view before closing the DataModel
view.reset();
}
void RobloxView::bindWorkspace(boost::shared_ptr<RBX::ViewBase> view, boost::shared_ptr<RBX::DataModel> const dataModel)
{
DataModel::LegacyLock lock(dataModel, RBX::DataModelJob::Write);
view->bindWorkspace(dataModel);
view->buildGui();
}
void RobloxView::setBounds(unsigned int width, unsigned int height)
{
this->width = width; this->height = height;
if (view)
view->onResize(width, height);
}
RobloxView *RobloxView::create_view(shared_ptr<RBX::Game> game, void* wnd, unsigned int width, unsigned int height)
{
RobloxView* result = new RobloxView(wnd, width,height);
result->completeViewPrep(game);
return result;
}
+77
View File
@@ -0,0 +1,77 @@
#pragma once
#include "boost/shared_ptr.hpp"
#include "boost/scoped_ptr.hpp"
#include "boost/thread.hpp"
#include "v8datamodel/game.h"
#include "Util/KeyCode.h"
#include "G3D/Vector2.h"
#include "rbx/signal.h"
namespace RBX
{
class DataModel;
class ViewBase;
class FunctionMarshaller;
class UserInputService;
namespace Tasks
{
class Sequence;
}
namespace Reflection
{
class PropertyDescriptor;
}
}
class RobloxView
{
boost::shared_ptr<RBX::ViewBase> view;
boost::shared_ptr<RBX::Game> game;
boost::scoped_ptr<class LeaveGameVerb> leaveGameVerb;
RBX::FunctionMarshaller* marshaller;
rbx::signals::scoped_connection placeIDChangeConnection;
boost::shared_ptr<RBX::Tasks::Sequence> sequence;
class RenderJob;
boost::shared_ptr<RenderJob> renderJob;
static boost::shared_ptr<RobloxView> rbxView;
void doTeleport(std::string url, std::string ticket, std::string script);
void onPlaceIDChanged(const RBX::Reflection::PropertyDescriptor* desc);
public:
RobloxView(void* wnd, unsigned int width, unsigned int height);
~RobloxView(void);
// request rendering stop as the app goes to background
void requestStopRenderingForBackgroundMode();
void requestResumeRendering();
void newGameDidStart();
void setBounds(unsigned int width, unsigned int height);
static RobloxView *create_view(shared_ptr<RBX::Game> game, void* wnd, unsigned int width, unsigned int height);
boost::shared_ptr<RBX::DataModel> getDataModel() { return game->getDataModel(); }
boost::shared_ptr<RBX::Game> getGame() { return game; }
boost::shared_ptr<RBX::ViewBase> getView() { return view; }
private:
unsigned int width;
unsigned int height;
void defineConcurrencyRules();
static void bindWorkspace(boost::shared_ptr<RBX::ViewBase> view, boost::shared_ptr<RBX::DataModel> const dataModel);
void completeViewPrep(shared_ptr<RBX::Game> game);
};
+48
View File
@@ -0,0 +1,48 @@
//
// RobloxWebUtilities.h
// RobloxMobile
//
// Created by Ben Tkacheff on 3/26/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "iOSSettingsService.h"
enum EnumButtons { ButtonGames = 10,
ButtonCatalog,
ButtonInventory,
ButtonBuildersClub,
ButtonProfile,
ButtonMessages,
TextFieldSearch,
ButtonGroups,
ButtonLeaderboards };
// the key we use to get settings
#define IOS_CLIENT_APP_SETTINGS_STRING "iOSAppSettings"
#define IOS_CLIENT_SETTINGS_API_KEY "D6925E56-BFB9-4908-AAA2-A5B1EC4B2D79"
@class StandardOutMessage;
@interface RobloxWebUtility : NSObject
{
iOSSettingsService cachediOSSettings;
NSDate *lastSettingsRequestTime;
BOOL bUpdating;
RBX::mutex updateLock;
std::string clientSettingsData;
std::string iOSAppSettingsData;
}
@property (nonatomic, readonly) BOOL bAppSettingsInitialized;
- (iOSSettingsService*) getCachediOSSettings;
- (void) updateAllClientSettingsWithCompletion:(void(^)())handler;
- (void) updateAllClientSettingsWithReporting:(BOOL)shouldReport withCompletion:(void(^)())handler;
- (void) writeUpdatedSettings;
+ (instancetype) sharedInstance;
@end
+223
View File
@@ -0,0 +1,223 @@
//
// RobloxWebUtility.mm
// RobloxMobile
//
// Created by Ben Tkacheff on 3/26/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import "RobloxWebUtility.h"
#import "RobloxInfo.h"
#import "RobloxGoogleAnalytics.h"
#import "RobloxCachedFlags.h"
#import "RobloxNotifications.h"
#import "v8datamodel/Game.h"
#import "RBXFunctions.h"
#import "SessionReporter.h"
// how long (in seconds) we store iosSettingsService
#define SETTINGS_CACHE_MAX_LIFETIME 300
#define CLIENT_SETTINGS_FILENAME "ClientAppSettings.json"
#define IOS_CLIENT_SETTINGS_FILENAME "iOSAppSettings.json"
@implementation RobloxWebUtility
+ (instancetype) sharedInstance
{
static dispatch_once_t onceToken;
static RobloxWebUtility *sharedRobloxWebUtility = nil;
dispatch_once(&onceToken, ^{
sharedRobloxWebUtility = [[self alloc] init];
});
return sharedRobloxWebUtility;
}
-(void) loadSettingsJSON:(const char *)group toObject:(RBX::FastLogJSON *)dest fromFile:(NSString *)filename
{
NSLog(@"RobloxWebUtility::loadSettingsJSON group %s filename %@", group, filename);
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentsDirectory = [paths objectAtIndex:0];
if (documentsDirectory)
{
NSString * filePath = [NSString stringWithFormat:@"%@/%@", documentsDirectory, filename];
NSError * error = 0;
NSString * appSettingsJSON = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
if (error)
{
NSLog(@"error loading %@: %@", filename, error);
}
else if (appSettingsJSON)
{
NSLog(@"loaded successfully");
std::string settingsData([appSettingsJSON cStringUsingEncoding:NSUTF8StringEncoding]);
LoadClientSettingsFromString(group, settingsData, dest);
}
else
{
NSLog(@"could not load %@", filename);
}
}
}
-(void) saveSettingsJSON:(std::string *)settingsData toFile:(NSString *)filename
{
NSLog(@"RobloxWebUtility::saveSettingsJSON filename %@", filename);
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentsDirectory = [paths objectAtIndex:0];
if (documentsDirectory)
{
NSString * filePath = [NSString stringWithFormat:@"%@/%@", documentsDirectory, filename];
NSString * appSettingsJSON = [NSString stringWithUTF8String:settingsData->c_str()];
if (appSettingsJSON)
{
NSError * error = nil;
[appSettingsJSON writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error)
{
NSLog(@"FAILED TO WRITE %@ JSON: %@", filename, error);
}
else
{
NSLog(@"saved successfully");
}
}
else
{
NSLog(@"failed to convert settingsData c-string to NSString!");
}
}
}
-(id) init
{
if(self = [super init])
{
RBX::Game::globalInit(false);
// set last time a long time ago
lastSettingsRequestTime = [NSDate dateWithTimeIntervalSince1970:0];
[self loadSettingsJSON:CLIENT_APP_SETTINGS_STRING toObject:&RBX::ClientAppSettings::singleton() fromFile:@CLIENT_SETTINGS_FILENAME];
[self loadSettingsJSON:IOS_CLIENT_APP_SETTINGS_STRING toObject:&RBX::ClientAppSettings::singleton() fromFile:@IOS_CLIENT_SETTINGS_FILENAME];
[self loadSettingsJSON:IOS_CLIENT_APP_SETTINGS_STRING toObject:&cachediOSSettings fromFile:@IOS_CLIENT_SETTINGS_FILENAME];
// Reset synchronized flags, they should be set by the server
FLog::ResetSynchronizedVariablesState();
}
return self;
}
- (iOSSettingsService*) getCachediOSSettings
{
return &cachediOSSettings;
}
- (void) writeUpdatedSettings
{
NSLog(@"RobloxWebUtility::writeUpdatedSettings - begin bUpdating:%d", bUpdating);
if (bUpdating)
{
// make sure we are not in game
NSString * gameState = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxGameState"];
if (gameState && ![gameState isEqualToString:@"tryGameJoin"]) // we are not joining/ingame/leaving
{
NSLog(@"RobloxGameState: %@ - not updating", gameState);
}
else
{
NSLog(@"updating...");
LoadClientSettingsFromString(CLIENT_APP_SETTINGS_STRING, clientSettingsData, &RBX::ClientAppSettings::singleton());
LoadClientSettingsFromString(IOS_CLIENT_APP_SETTINGS_STRING, iOSAppSettingsData, &RBX::ClientAppSettings::singleton());
LoadClientSettingsFromString(IOS_CLIENT_APP_SETTINGS_STRING, iOSAppSettingsData, &cachediOSSettings);
bUpdating = false;
}
}
NSLog(@"RobloxWebUtility::writeUpdatedSettings - end");
}
- (void) updateAllClientSettingsWithReporting:(BOOL)shouldReport withCompletion:(void(^)())handler;
{
NSLog(@"RobloxWebUtility::updateAllClientSettingsWithReport:%d - begin ", shouldReport);
NSLog(@"- fetching settings from %@ using baseURL %@", [RobloxInfo getEnvironmentName:NO], [RobloxInfo getBaseUrl]);
{
RBX::mutex::scoped_lock lock(updateLock);
if (bUpdating)
{
NSLog(@"already updating.");
return;
}
bUpdating = true;
}
NSDate* reportingStartTime;
NSTimeInterval clientFetchTime;
NSTimeInterval appFetchTime;
clientSettingsData.assign("");
iOSAppSettingsData.assign("");
// Set our fast logs settings
reportingStartTime = [NSDate date];
FetchClientSettingsData(CLIENT_APP_SETTINGS_STRING, CLIENT_SETTINGS_API_KEY, &clientSettingsData);
clientFetchTime = [[NSDate date] timeIntervalSinceDate:reportingStartTime];
reportingStartTime = [NSDate date];
FetchClientSettingsData(IOS_CLIENT_APP_SETTINGS_STRING, IOS_CLIENT_SETTINGS_API_KEY, &iOSAppSettingsData);
appFetchTime = [[NSDate date] timeIntervalSinceDate:reportingStartTime];
//report how long it takes to complete these calls
if (shouldReport)
{
[[SessionReporter sharedInstance] postStartupPayloadForEvent:@"fetchClientSettings" completionTime:clientFetchTime];
[[SessionReporter sharedInstance] postStartupPayloadForEvent:@"fetchAppSettings" completionTime:appFetchTime];
}
lastSettingsRequestTime = [NSDate date];
[[RobloxCachedFlags sharedInstance] setInt:@"CrashlyticsPercentage" withValue:cachediOSSettings.GetValueCrashlyticsPercentage()];
// debug print
NSLog(@"CrashlyticsPercentage: %d", cachediOSSettings.GetValueCrashlyticsPercentage());
[[RobloxCachedFlags sharedInstance] sync];
_bAppSettingsInitialized = true;
[self saveSettingsJSON:&clientSettingsData toFile:@CLIENT_SETTINGS_FILENAME];
[self saveSettingsJSON:&iOSAppSettingsData toFile:@IOS_CLIENT_SETTINGS_FILENAME];
// write them in the main thread to be safe
[RBXFunctions dispatchOnMainThread:^{
[self writeUpdatedSettings];
[[NSNotificationCenter defaultCenter] postNotificationName:RBX_NOTIFY_FFLAGS_UPDATED object:nil];
if (![RBXFunctions isEmpty:handler])
handler();
}];
NSLog(@"RobloxWebUtility::updateAllClientSettingsWithWrite - end");
}
- (void) updateAllClientSettingsWithCompletion:(void(^)(void))handler
{
[self updateAllClientSettingsWithReporting:NO withCompletion:handler];
}
@end
+45
View File
@@ -0,0 +1,45 @@
//
// SessionReporter.h
// RobloxMobile
//
// Created by Ganesh Agrawal on 7/12/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RBXEventReporter.h"
@interface SessionReporter : NSObject
typedef enum
{ APPLICATION_ACTIVE,
APPLICATION_BACKGROUND,
GAME_RUNNING,
ENTER_GAME,
EXIT_GAME,
OUT_MEMORY_ON_LOAD,
OUT_MEMORY_IN_GAME,
APPLICATION_FRESH_START
} ApplicationState;
+(id) sharedInstance;
-(void) reportSessionForContext:(RBXAnalyticsContextName)context result:(RBXAnalyticsResult)result errorName:(RBXAnalyticsErrorName)errorName responseCode:(NSInteger)responseCode data:(NSDictionary *)dataDictionary;
-(void) reportSessionFor:(ApplicationState) appState PlaceId:(NSInteger) placeId;
-(void) reportSessionFor:(ApplicationState) appState;
-(void) postAnalyticPayloadFromContext:(RBXAnalyticsContextName)context
result:(RBXAnalyticsResult)result
rbxError:(RBXAnalyticsErrorName)rbxError
startingTime:(NSDate*)startTime
attemptedUsername:(NSString*)username
URLRequest:(NSURLRequest*)request
HTTPResponseCode:(NSInteger)responseCode
responseData:(NSData*)responseData
additionalData:(NSDictionary*)additionalData;
-(void) postStartupPayloadForEvent:(NSString*)requestName
completionTime:(NSTimeInterval)timeMS;
-(void) postAutoLoginFailurePayload:(NSTimeInterval)loginTimestamp
cookieExpiration:(NSTimeInterval)actualExpirationTimestamp
expectedExpiration:(NSTimeInterval)expectedExpirationTimestamp;
@end
+737
View File
@@ -0,0 +1,737 @@
//
// SessionReporter.m
// RobloxMobile
//
// Created by Ganesh Agrawal on 7/12/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import "SessionReporter.h"
#import "RobloxGoogleAnalytics.h"
#import "RobloxInfo.h"
#import "PlaceLauncher.h"
#import "UserInfo.h"
#import "iOSSettingsService.h"
#import "RobloxMemoryManager.h"
#import "RobloxNotifications.h"
#import "RBXFunctions.h"
#import "v8datamodel/Stats.h"
#import "NSDictionary+Parsing.h"
#define KEY_PLACE_ID "PlaceId"
#define KEY_GAME_START_TIME "GameStartTime"
#define KEY_GAME_END_TIME "GameEndTime"
#define KEY_APP_CRASH "CRASHED"
#define KEY_FREE_MEMORY "FreeMemory"
#define KEY_USED_MEMORY "UsedMemory"
#define KEY_NUM_MEM_WARNING "NumMemoryWarning"
#define KEY_LAST_OUT_OF_MEM_TIME "LastOutOfMemWarningTime"
#define APP_STATUS_SUCCESS "AppStatusSuccess"
#define APP_STATUS_CRASH "AppStatusCrash"
#define APP_OUT_OF_MEM_ON_LOAD "AppStatusOutOfMemoryOnLoad"
#define APP_OUT_OF_MEM_IN_GAME "AppStatusOutOfMemoryInGame"
DYNAMIC_FASTINTVARIABLE(iOSInfluxHundredthsPercentage, 1000)
DYNAMIC_FASTINTVARIABLE(GenerateOutOfMemoryReportIfBelowMB, 10)
static int calculateSessionDataEveryMilliSeconds = 100;
@implementation SessionReporter
+ (id)sharedInstance
{
static dispatch_once_t rbxSessionReporterFlagsPred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&rbxSessionReporterFlagsPred, ^{ // Need to use GCD for thread-safe allocation
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-(id) init
{
if(self = [super init])
{
}
return self;
}
-(void)pushSessionData: (NSString*) eventType PlaceId:(NSInteger) placeId GamePlayTime:(NSInteger) playTime
{
RBX::Analytics::InfluxDb::Points points;
points.addPoint("SessionReport" , [eventType UTF8String]);
[RobloxGoogleAnalytics setEventTracking:@"SessionReport" withAction:eventType withLabel:[NSString stringWithFormat:@"%ld", (long)placeId] withValue:playTime];
NSString *appVersion = [[NSUserDefaults standardUserDefaults] stringForKey:@"LastRunningAppVersion"];
if (!appVersion)
appVersion = [RobloxInfo appVersion];
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"LastUserLoggedIn"])
{
points.addPoint("LastUserLoggedIn" , [[[NSUserDefaults standardUserDefaults] stringForKey:@"LastUserLoggedIn"] UTF8String]);
RBX::Analytics::setUserId([[NSUserDefaults standardUserDefaults] integerForKey:@"LastUserIDLoggedIn"]);
}
if([eventType isEqualToString:@APP_STATUS_CRASH])
[RobloxGoogleAnalytics setEventTracking:@"CrashCountByUser"
withAction:appVersion
withLabel:[[NSUserDefaults standardUserDefaults] stringForKey:@"LastUserLoggedIn"]
withValue:0];
NSString *gameStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxGameState"];
NSString *appStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxAppState"];
if (gameStateString || appStateString)
{
NSString * crashStateString = [[NSUserDefaults standardUserDefaults] stringForKey:(gameStateString ? @"RobloxGameState" : @"RobloxAppState")];
points.addPoint("GameState" , [crashStateString UTF8String]);
points.addPoint("FreeMemoryKB" , (int)[[NSUserDefaults standardUserDefaults] integerForKey:@KEY_FREE_MEMORY]);
points.addPoint("UsedMemoryKB" , (int)[[NSUserDefaults standardUserDefaults] integerForKey:@KEY_USED_MEMORY]);
if (gameStateString)
{
RBX::Analytics::setPlaceId(placeId);
points.addPoint("PlayTime" , (int)playTime);
points.addPoint("NumMemoryWarning" , (int)[[NSUserDefaults standardUserDefaults] integerForKey:@KEY_NUM_MEM_WARNING]);
// If we had received some memory warnings before crashing
if ([eventType isEqualToString:@APP_STATUS_CRASH] && [[NSUserDefaults standardUserDefaults] objectForKey:@KEY_LAST_OUT_OF_MEM_TIME])
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_GAME_END_TIME])
{
int lastMemWarningMSec = 1000 * ([[NSUserDefaults standardUserDefaults] doubleForKey:@KEY_GAME_END_TIME] - [[NSUserDefaults standardUserDefaults] doubleForKey:@KEY_LAST_OUT_OF_MEM_TIME]);
points.addPoint("MillSecMemoryWarn" , lastMemWarningMSec);
}
}
}
}
points.report("iOS-RobloxPlayer-SessionReport", DFInt::iOSInfluxHundredthsPercentage);
if ([eventType isEqualToString:@APP_STATUS_CRASH] && [[NSUserDefaults standardUserDefaults] integerForKey:@KEY_FREE_MEMORY] < (DFInt::GenerateOutOfMemoryReportIfBelowMB * 1024) && gameStateString)
eventType = [gameStateString isEqualToString:@"inGame"] ? @APP_OUT_OF_MEM_IN_GAME : @APP_OUT_OF_MEM_ON_LOAD;
NSString* apiUrl = [NSString stringWithFormat:@"%@/game/sessions/report?placeId=%ld&eventType=%@", [RobloxInfo getApiBaseUrl], (long)placeId, eventType];
NSURL *url = [NSURL URLWithString: apiUrl];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60*7];
[RobloxInfo setDefaultHTTPHeadersForRequest:theRequest];
[theRequest setHTTPMethod:@"POST"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:theRequest queue:queue completionHandler:
^(NSURLResponse *response, NSData *receiptResponseData, NSError *error){/*Do not care for response*/}];
}
- (BOOL) getPlayData: (NSInteger&) placeId PlayTimeSeconds: (NSInteger&) gamePlayTime CalculateNow: (BOOL) endNow
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_PLACE_ID])
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_GAME_START_TIME])
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_GAME_END_TIME])
gamePlayTime = [[NSUserDefaults standardUserDefaults] doubleForKey:@KEY_GAME_END_TIME] - [[NSUserDefaults standardUserDefaults] doubleForKey:@KEY_GAME_START_TIME];
else if (endNow)
gamePlayTime = [[NSDate date] timeIntervalSince1970] - [[NSUserDefaults standardUserDefaults] doubleForKey:@KEY_GAME_START_TIME];
placeId = [[NSUserDefaults standardUserDefaults] integerForKey:@KEY_PLACE_ID];
}
return YES;
}
return NO;
}
-(void) callTimerFn
{
if([[PlaceLauncher sharedInstance] getIsCurrentlyPlayingGame])
{
[self reportSessionFor:GAME_RUNNING];
[self performSelector:@selector(callTimerFn) withObject:nil afterDelay:((NSTimeInterval)calculateSessionDataEveryMilliSeconds/1000)];
}
}
-(void) reportSessionFor:(ApplicationState) appState
{
[self reportSessionFor:appState PlaceId:0];
}
-(void) postAnalyticPayloadFromContext:(RBXAnalyticsContextName)context
result:(RBXAnalyticsResult)result
rbxError:(RBXAnalyticsErrorName)rbxError
startingTime:(NSDate*)startTime
attemptedUsername:(NSString*)username
URLRequest:(NSURLRequest*)request
HTTPResponseCode:(NSInteger)responseCode
responseData:(NSData*)responseData
additionalData:(NSDictionary*)additionalData
{
[RBXFunctions dispatchOnBackgroundThread:^{
//NOTE - REQUIRE ALL INPUT INTO THE DICTIONARY TO BE STRINGS
NSMutableDictionary<NSString*, NSString*> *analyticsPayload = [NSMutableDictionary dictionary];
//NSTimeInterval is a measurement given in seconds, be sure to cast it to milliseconds
NSTimeInterval responseTime = MAX(0, ([[NSDate date] timeIntervalSinceDate:startTime] * 1000));
[analyticsPayload setObject:[NSString stringWithFormat:@"%d", (int)responseTime] forKey:@"responseTimeMs"];
//Keep track of the type of action we are doing
if (context == RBXAContextLogin || context == RBXAContextSocialLogin || context == RBXAContextAppLaunch)
{
//NOTE - REQUIRES ADDITIONAL DATA
NSString* loginType = @"manual";
bool performingAutoLogin = [additionalData boolValueForKey:@"isAutoLogin" withDefault:NO];
if (context == RBXAContextSocialLogin && performingAutoLogin == YES) loginType = @"socialAuto";
else if (context == RBXAContextSocialLogin && performingAutoLogin == NO) loginType = @"social";
else if (context != RBXAContextSocialLogin && performingAutoLogin == YES) loginType = @"auto";
[analyticsPayload setObject:loginType forKey:@"loginType"];
}
else if (context == RBXAContextSocialLogin || context == RBXAContextSignup)
{
NSString* signupType = (context == RBXAContextSignup) ? @"regular" : @"social";
[analyticsPayload setObject:signupType forKey:@"signupType"];
}
else if (context == RBXAContextSettingsSocial)
{
// NOTE - REQUIRES ADDITIONAL DATA
//save what social network we are connecting to
NSString* socialNetworkName = [additionalData stringForKey:@"provider" withDefault:nil];
if (socialNetworkName)
[analyticsPayload setObject:socialNetworkName forKey:@"provider"];
}
if (result == RBXAResultFailure) {
//Failure reason
[analyticsPayload setObject:[[RBXEventReporter sharedInstance] nameForError:rbxError] forKey:@"Status"];
//Request URL
if (![RBXFunctions isEmpty:request.URL.description])
[analyticsPayload setObject:request.URL.description forKey:@"requestUrl"];
//Response body as a string, but clean it up so it reports properly
NSString *responseBody = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
responseBody = [responseBody stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
responseBody = [responseBody stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
responseBody = [responseBody stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; //replace new line with space
responseBody = [responseBody stringByReplacingOccurrencesOfString:@"\r" withString:@" "]; //replace line return with space
if (![RBXFunctions isEmpty:responseBody])
[analyticsPayload setObject:responseBody forKey:@"responseBody"];
// Username <- simply pulling it from the UserInfo object is not accurate
if (![RBXFunctions isEmpty:username])
[analyticsPayload setObject:username forKey:@"username"];
// Browser Tracker Id
if (![RBXFunctions isEmpty:[RobloxData browserTrackerId]])
[analyticsPayload setObject:[RobloxData browserTrackerId] forKey:@"browserTrackerId"];
// App Version
if (![RBXFunctions isEmpty:[RobloxInfo appVersion]])
[analyticsPayload setObject:[RobloxInfo appVersion] forKey:@"appVersion"];
// Device Type
if (![RBXFunctions isEmpty:[RobloxInfo deviceType]])
[analyticsPayload setObject:[RobloxInfo deviceType] forKey:@"deviceType"];
// OS Version
if (![RBXFunctions isEmpty:[RobloxInfo deviceOSVersion]])
[analyticsPayload setObject:[RobloxInfo deviceOSVersion] forKey:@"deviceOSVersion"];
//HTTP Response Code
if (responseCode)
[analyticsPayload setObject:[NSString stringWithFormat:@"%ld", (long)responseCode] forKey:@"httpResponseCode"];
}
// Post analytics information
[self reportSessionForContext:context
result:result
errorName:rbxError
responseCode:responseCode
data:analyticsPayload];
}];
}
-(void) postStartupPayloadForEvent:(NSString*)requestName
completionTime:(NSTimeInterval)timeSeconds
{
[RBXFunctions dispatchOnBackgroundThread:^{
NSNumber* timeNumber = [NSNumber numberWithInt:(int)(timeSeconds * 1000.0)];
[self sendSessionDataToInfluxSeries:@"AppStartupTimeiOS"
data:@{@"requestName":requestName,
@"completionTime":timeNumber.stringValue}];
}];
}
-(void) postAutoLoginFailurePayload:(NSTimeInterval)loginTimestamp
cookieExpiration:(NSTimeInterval)actualExpirationTimestamp
expectedExpiration:(NSTimeInterval)expectedExpirationTimestamp
{
[RBXFunctions dispatchOnBackgroundThread:^{
//create a number formatter to handle the conversion from timeInterval to string
NSNumberFormatter* nf = [[NSNumberFormatter alloc] init];
[nf setMaximumFractionDigits:0];
[nf setRoundingMode:NSNumberFormatterRoundHalfEven];
//convert NSTimeInterval from seconds to milliseconds
NSNumber* numLogin = [NSNumber numberWithDouble:(double)loginTimestamp * 1000.0] ;
NSNumber* numActual = [NSNumber numberWithDouble:(double)actualExpirationTimestamp * 1000.0] ;
NSNumber* numExpected = [NSNumber numberWithDouble:(double)expectedExpirationTimestamp * 1000.0];
//report the data to Influx
[self sendSessionDataToInfluxSeries:@"AutoLoginFailures"
data:@{@"initialLoginTimestamp":[nf stringFromNumber:numLogin],
@"cookieExpirationTimestamp":[nf stringFromNumber:numActual],
@"expectedCookieExpirationTimestamp":[nf stringFromNumber:numExpected]}];
}];
}
-(void) sendSessionDataToInfluxSeries:(NSString*)seriesName data:(NSDictionary<NSString*, NSString*>*)dataDictionary
{
// Post result to Influx
RBX::Analytics::InfluxDb::Points analyticsPoints;
for (NSString *key in dataDictionary) {
NSString* obj = (NSString*)[dataDictionary objectForKey:key];
//NSLog(@"Session Data [%s] = %@", [key UTF8String], obj);
analyticsPoints.addPoint(std::string([key UTF8String]).c_str(), [obj UTF8String]);
}
analyticsPoints.report(std::string([seriesName UTF8String]), 10000, true);
}
-(void) reportSessionForContext:(RBXAnalyticsContextName)context result:(RBXAnalyticsResult)result errorName:(RBXAnalyticsErrorName)errorName responseCode:(NSInteger)responseCode data:(NSDictionary *)dataDictionary
{
//Log In Documentation Reference :
//Sign Up Documentation Reference : https://docs.google.com/document/d/1q2T4--wtMgDtv8Nnv9xJm3V0rJLeXB0hn1GpjnNnEJk/edit?ts=5627cc46
/*
here are the counter names for diag:
diag: iOS-AppLogin-Success
diag: iOS-AppLogin-Failure
diag: iOS-SocialLogin-Success
diag: iOS-SocialLogin-Failure
diag: iOS-AppSignup-Success
diag: iOS-AppSignup-Failure
diag: iOS-SocialSignup-Success
diag: iOS-SocialSignup-Failure
value will be 1 for each report
*/
//sanitize some inputs
if (result == RBXAResultFailure) {
if ([RBXFunctions isEmpty:@(errorName)]) {
errorName = RBXAErrorNoError;
}
}
NSString* GAContextName = nil;
switch (context) {
//login metrics
case RBXAContextLogin:
{
// Post result to Google Analytics
GAContextName = [[RBXEventReporter sharedInstance] nameForContext:context]; //"login"
//Diag
if (result == RBXAResultSuccess) {
RBX::Analytics::EphemeralCounter::reportCounter("iOS-AppLogin-Success", 1);
}
else if (result == RBXAResultFailure) {
RBX::Analytics::EphemeralCounter::reportCounter("iOS-AppLogin-Failure", 1);
// Post additional details to Influx
[self sendSessionDataToInfluxSeries:@"LoginFailureiOS" data:dataDictionary];
}
} break;
// Social Login metrics
case RBXAContextSocialLogin:
{
// Post result to Google Analytics
GAContextName = [[RBXEventReporter sharedInstance] nameForContext:context]; //"socialLogin"
//Diag
if (result == RBXAResultSuccess) {
RBX::Analytics::EphemeralCounter::reportCounter("iOS-SocialLogin-Success", 1);
}
else if (result == RBXAResultFailure) {
RBX::Analytics::EphemeralCounter::reportCounter("iOS-SocialLogin-Failure", 1);
// Post additional details to Influx
[self sendSessionDataToInfluxSeries:@"LoginFailureiOS" data:dataDictionary];
}
} break;
// Automatic login success metrics
case RBXAContextAppLaunch:
{
// Post result to Google Analytics
GAContextName = [[RBXEventReporter sharedInstance] nameForContext:context]; //"appLaunch"
// Diag
if (responseCode == 999)
{
RBX::Analytics::EphemeralCounter::reportCounter("iOS-inAppBrowserTrackerChanged-Success", 1);
//Nothing is being done with the Data dictionary ??
}
} break;
//Signup metrics
case RBXAContextSignup:
{
// Post result to Google Analytics
GAContextName = @"SignupAttempt";
//Diag
if (result == RBXAResultSuccess) {
RBX::Analytics::EphemeralCounter::reportCounter("iOS-AppSignup-Success", 1);
}
else if (result == RBXAResultFailure) {
RBX::Analytics::EphemeralCounter::reportCounter("iOS-AppSignup-Failure", 1);
// Post additional details to Influx
[self sendSessionDataToInfluxSeries:@"SignupFailureiOS" data:dataDictionary];
}
} break;
//Social Signup metrics
case RBXAContextSocialSignup:
{
// Post result to Google Analytics
GAContextName = @"SocialSignupAttempt";
//Diag
if (result == RBXAResultSuccess) {
RBX::Analytics::EphemeralCounter::reportCounter("iOS-SocialSignup-Success", 1);
}
else if (result == RBXAResultFailure) {
RBX::Analytics::EphemeralCounter::reportCounter("iOS-SocialSignup-Failure", 1);
// Post additional details to Influx
[self sendSessionDataToInfluxSeries:@"SignupFailureiOS" data:dataDictionary];
}
} break;
// Social Connect / Disconnect metrics
case RBXAContextSettingsSocialConnect:
{
GAContextName = @"SocialConnectAttempt";
if (result == RBXAResultFailure)
{
// Post additional details to Influx
[self sendSessionDataToInfluxSeries:@"SocialConnectFailureiOS" data:dataDictionary];
}
} break;
case RBXAContextSettingsSocialDisconnect:
{
GAContextName = @"SocialDisconnectAttempt";
if (result == RBXAResultFailure)
{
// Post additional details to Influx
[self sendSessionDataToInfluxSeries:@"SocialDisconnectFailureiOS" data:dataDictionary];
}
} break;
// Application Did Enter Foreground
case RBXAContextAppEnterForeground:
{
// Post result to Google Analytics
GAContextName = [[RBXEventReporter sharedInstance] nameForContext:context];
//Diag
if (result == RBXAResultFailure) {
RBX::Analytics::EphemeralCounter::reportCounter("iOS-AppEnterForeground-Failure", 1);
// Post additional details to Influx
[self sendSessionDataToInfluxSeries:@"AppEnterForegroundFailureiOS" data:dataDictionary];
}
} break;
//for all other cases, do nothing
default:
break;
}
//Report to GA
if (GAContextName)
{
NSString* GAReportString = (result == RBXAResultSuccess) ? @"Success" : [[RBXEventReporter sharedInstance] nameForError:errorName];
[RobloxGoogleAnalytics setEventTracking:GAContextName
withAction:GAReportString
withLabel:[NSString stringWithFormat:@"%ld", (long)responseCode]
withValue:0];
}
}
-(void) reportSessionFor:(ApplicationState) appState PlaceId:(NSInteger) idPlace
{
switch (appState)
{
case APPLICATION_FRESH_START:
{
NSString *gameStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxGameState"];
NSString *appStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxAppState"];
if (gameStateString || appStateString)
{
NSString * crashStateString = [[NSUserDefaults standardUserDefaults] stringForKey:(gameStateString ? @"RobloxGameState" : @"RobloxAppState")];
[RobloxGoogleAnalytics debugCounterIncrement:crashStateString];
// create "terminated_gameStateString" string if needed
if (appStateString && [appStateString isEqualToString:@"terminated"])
{
if (gameStateString)
crashStateString = [NSString stringWithFormat:@"terminated_%@", gameStateString];
}
NSString *appVersion = [[NSUserDefaults standardUserDefaults] stringForKey:@"LastRunningAppVersion"];
if (!appVersion)
appVersion = [RobloxInfo appVersion];
[RobloxGoogleAnalytics setEventTracking:@"CrashState"
withAction:appVersion
withLabel:crashStateString
withValue:0];
// MemState reporting
NSString *memMgrStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxMemMgrState"];
if (memMgrStateString)
{
[RobloxGoogleAnalytics setEventTracking:@"RobloxMemMgrState"
withAction:appVersion
withLabel:memMgrStateString
withValue:0];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"RobloxMemMgrState"];
if ([memMgrStateString isEqualToString:@"Bouncer"])
[[RobloxMemoryManager sharedInstance] memBouncerCrashed];
}
if (gameStateString)
{
NSInteger placeId, gamePlayTime = 0;
BOOL hasPlayData = [self getPlayData:placeId PlayTimeSeconds:gamePlayTime CalculateNow:NO];
if (hasPlayData)
{
// Do not report to Stats if the Player did not crash, we do similar on Mac/PC/Android
// App Crash will not be considered into MTBF
[self pushSessionData:@APP_STATUS_CRASH PlaceId:placeId GamePlayTime:gamePlayTime];
RBX::Analytics::EphemeralCounter::reportCounter("iOS-ROBLOXPlayer-Crash", 1, true);
RBX::Analytics::EphemeralCounter::reportCounter("ROBLOXPlayer-Crash", 1, true);
}
}
else
[self pushSessionData:@APP_STATUS_CRASH PlaceId:0 GamePlayTime:0];
[RobloxGoogleAnalytics setEventTracking:@APP_STATUS_CRASH withAction:@"UserAgent" withLabel:[RobloxInfo getUserAgentString] withValue:0];
}
gameStateString = nil;
appStateString = nil;
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"RobloxGameState"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"RobloxAppState"];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSUserDefaults standardUserDefaults] setObject:[RobloxInfo appVersion] forKey:@"LastRunningAppVersion"];
// Since we have reported the last session data time to clear it up.
[self clearSession];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(callTimerFn) object:nil];
break;
}
case APPLICATION_ACTIVE:
{
NSInteger placeId, gamePlayTime = 0;
BOOL hasPlayData = [self getPlayData:placeId PlayTimeSeconds:gamePlayTime CalculateNow:NO];
if(hasPlayData)
{
NSString* eventType = [NSString stringWithFormat:@"%s", APP_STATUS_SUCCESS];
[self pushSessionData:eventType PlaceId:placeId GamePlayTime:gamePlayTime];
}
// Since we have reported the last session data time to clear it up.
[self clearSession];
//Scenario where user was in game, double clicked the home buton & then came back quickly and still in Game.
NSString *gameStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxGameState"];
if([gameStateString isEqualToString:@"inGame"])
{
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_START_TIME];
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_END_TIME];
[[NSUserDefaults standardUserDefaults] setInteger:placeId forKey:@KEY_PLACE_ID];
[[NSUserDefaults standardUserDefaults] synchronize];
}
break;
}
case APPLICATION_BACKGROUND:
{
NSInteger placeId, gamePlayTime = 0;
BOOL hasPlayData = [self getPlayData:placeId PlayTimeSeconds:gamePlayTime CalculateNow:YES];
// Report if we have gamePlayTime available
// This will be a successful exit case & would never be a crash case
// Report State, report successState state only if placeId is not zero & gamePlayTime is not zero
if(hasPlayData && gamePlayTime)
[self pushSessionData:@APP_STATUS_SUCCESS PlaceId:placeId GamePlayTime:gamePlayTime];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(callTimerFn) object:nil];
// Since we have reported the last session data time to clear it up.
[self clearSession];
break;
}
case GAME_RUNNING:
{
if([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_GAME_START_TIME])
{
int kiloBytesFree = freeMemory()/1024.0f;
int kiloBytesUsed = usedMemory()/1024.0f;
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesFree forKey:@KEY_FREE_MEMORY];
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesUsed forKey:@KEY_USED_MEMORY];
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_END_TIME];
[[NSUserDefaults standardUserDefaults] synchronize];
}
break;
}
case ENTER_GAME:
{
int kiloBytesFree = freeMemory()/1024.0f;
int kiloBytesUsed = usedMemory()/1024.0f;
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesFree forKey:@KEY_FREE_MEMORY];
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesUsed forKey:@KEY_USED_MEMORY];
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_START_TIME];
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_END_TIME];
[[NSUserDefaults standardUserDefaults] setInteger:idPlace forKey:@KEY_PLACE_ID];
[[NSUserDefaults standardUserDefaults] synchronize];
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
calculateSessionDataEveryMilliSeconds = iosSettings->GetValueCalculateSessionReportEveryMilliSeconds();
// Keep recording Game End Time, as will be useful in the event of Crash
dispatch_sync(dispatch_get_main_queue(), ^{
[self performSelector:@selector(callTimerFn) withObject:nil afterDelay:((NSTimeInterval)calculateSessionDataEveryMilliSeconds/1000)];
});
break;
}
case EXIT_GAME:
case OUT_MEMORY_ON_LOAD:
case OUT_MEMORY_IN_GAME:
{
if([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_GAME_START_TIME])
{
int kiloBytesFree = freeMemory()/1024.0f;
int kiloBytesUsed = usedMemory()/1024.0f;
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesFree forKey:@KEY_FREE_MEMORY];
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesUsed forKey:@KEY_USED_MEMORY];
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_END_TIME];
}
if (appState != EXIT_GAME) // It is not a Exit, but a memory warning & we are continuing to play the Game
{
int numOutOfMemWarning = [[NSUserDefaults standardUserDefaults] integerForKey:@KEY_NUM_MEM_WARNING];
[[NSUserDefaults standardUserDefaults] setInteger:++numOutOfMemWarning forKey:@KEY_NUM_MEM_WARNING];
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_LAST_OUT_OF_MEM_TIME];
}
[[NSUserDefaults standardUserDefaults] synchronize];
NSInteger placeId = 0;
NSInteger gamePlayTime = 0;
BOOL hasPlayData = [self getPlayData:placeId PlayTimeSeconds:gamePlayTime CalculateNow:YES];
if (appState == EXIT_GAME) // It is a Clean Game Exit, Not a memory warning
{
if (hasPlayData)
{
[RobloxInfo reportMaxMemoryUsedForPlaceID:placeId];
[self pushSessionData:@APP_STATUS_SUCCESS PlaceId:placeId GamePlayTime:gamePlayTime];
}
[[NSNotificationCenter defaultCenter] postNotificationName:RBX_NOTIFY_DID_LEAVE_GAME object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(callTimerFn) object:nil];
[self clearSession];
}
break;
}
default:
break;
}
}
-(void) clearSession
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_GAME_START_TIME];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_GAME_END_TIME];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_PLACE_ID];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_FREE_MEMORY];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_USED_MEMORY];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_NUM_MEM_WARNING];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_LAST_OUT_OF_MEM_TIME];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ExceptionName"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ExceptionDesc"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ExceptionReason"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ExceptionUserInfo"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ExceptionCallStackTop"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
@end
+39
View File
@@ -0,0 +1,39 @@
//
// SignupVerifier.h
// RobloxMobile
//
// Created by Ben Tkacheff on 2/5/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef enum {
GENDER_DEFAULT,
GENDER_BOY,
GENDER_GIRL
} Gender;
typedef void(^SignupVerifierCompletionHandler)(BOOL success, NSString *message);
@interface SignupVerifier : NSObject
+ (instancetype) sharedInstance;
// Helper functions
- (NSString *) obfuscateEmail:(NSString *)emailAddress;
- (void) checkIfValidUsername:(NSString*)username completion:(SignupVerifierCompletionHandler)handler;
- (void) checkIfValidPassword:(NSString*)password withUsername:(NSString*)username completion:(SignupVerifierCompletionHandler)handler;
- (void) checkIfPasswordsMatch:(NSString*)password withVerification:(NSString*)verify completion:(SignupVerifierCompletionHandler)handler;
- (void) checkIfValidEmail:(NSString*)email completion:(SignupVerifierCompletionHandler)handler;
- (void) getAlternateUsername:(NSString *)username completion:(SignupVerifierCompletionHandler)handler;
- (void) signUpWithUsername:(NSString *)username
password:(NSString *)password
birthString:(NSString *)birthString
gender:(Gender)gender
email:(NSString *)email
completionBlock:(void (^)(NSError *signUpError))completionBlock;
@end
+653
View File
@@ -0,0 +1,653 @@
//
// SignupVerifier.m
// RobloxMobile
//
// Created by Ben Tkacheff on 2/5/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import "SignupVerifier.h"
#import <AdSupport/AdSupport.h>
#import <CommonCrypto/CommonHMAC.h>
#import "iOSSettingsService.h"
#import "LoginManager.h"
#import "NSDictionary+Parsing.h"
#import "RBXEventReporter.h"
#import "RBXFunctions.h"
#import "RobloxData.h"
#import "RobloxInfo.h"
#import "RobloxWebUtility.h"
#import "SessionReporter.h"
#import "UserInfo.h"
DYNAMIC_FASTFLAGVARIABLE(EnableXBOXSignupRules, false);
@interface SignupVerifier ()
@property (retain, nonatomic) NSString *signUpUrlString;
@property (retain, nonatomic) NSString *signUpArgs;
@end
@implementation SignupVerifier
NSString* s3 = @"Q,v?KZ^#q";
NSString* s1 = @"Fu.*mJ";
NSString* s4 = @"l%=f~RIWh";
NSString* s2 = @"L65H";
NSString* s5 = @"C39$";
NSString* s8 = @"Av=MHZ";
NSString* s6 = @"jEda0J~iq";
NSString* s9 = @"mfcyG9,F";
NSString* s7 = @"b@Wl";
NSString* s10 = @"B7YpO";
#pragma mark Constructors
+(id) sharedInstance
{
static dispatch_once_t rbxSignUpVerifierManPred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&rbxSignUpVerifierManPred, ^{ // Need to use GCD for thread-safe allocation
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-(id) init
{
if(self = [super init])
{
// have to use www base url
NSString* baseUrl = [[RobloxInfo getBaseUrl] stringByReplacingOccurrencesOfString:@"://m." withString:@"://www."];
baseUrl = [baseUrl stringByReplacingOccurrencesOfString:@"http" withString:@"https"];
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
bool bSignUpWithHash = iosSettings->GetValueSignUpWithHash();
if(bSignUpWithHash)
self.signUpUrlString = [baseUrl stringByAppendingString:@"mobileapi/securesignup"];
else
self.signUpUrlString = [baseUrl stringByAppendingString:@"mobileapi/signup"];
self.signUpArgs = @"userName=%@&password=%@&gender=%@&dateOfBirth=%@&advertiserID=%@";
}
return self;
}
#pragma mark Flag Settings
-(bool) usingNewSignupRules { return DFFlag::EnableXBOXSignupRules; }
#pragma mark - Email
-(NSString *)obfuscateEmail:(NSString *)emailAddress
{
NSRange rangeOfAtChar = [emailAddress rangeOfString:@"@"];
if (rangeOfAtChar.location != NSNotFound)
{
NSRange emailNameRange = NSMakeRange(0, rangeOfAtChar.location);
NSMutableString* hiddenCharacters = [NSMutableString stringWithString:@""];
for (int i = 0; i < emailNameRange.length; i++)
[hiddenCharacters appendString:@"*"];
// return the obscured email
return [emailAddress stringByReplacingCharactersInRange:emailNameRange withString:hiddenCharacters];
}
//doesn't look like a real email address to me, so just return the string
return emailAddress;
}
-(bool) isValidEmail:(NSString*)email
{
NSString *expression = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:expression options:NSRegularExpressionCaseInsensitive error:&error];
if(error)
return NO;
NSTextCheckingResult *isEmail = [regex firstMatchInString:email options:0 range:NSMakeRange(0, [email length])];
return isEmail;
}
- (void) checkIfValidEmail:(NSString*)email completion:(SignupVerifierCompletionHandler)handler
{
if (![self isValidEmail:email])
{
[[RBXEventReporter sharedInstance] reportFormFieldValidation:RBXAFieldEmail
withContext:RBXAContextSignup
withError:RBXAErrorTooShort];
handler(NO, NSLocalizedString(@"EmailInvalid", nil));
}
else
{
//check the server if the username is not blacklisted
[RobloxData checkIfBlacklistedEmail:email
completion:^(BOOL isBlacklisted)
{
if (!isBlacklisted)
{
//looks like the user has a good email!
handler(YES, nil);
}
else
{
handler(NO, NSLocalizedString(@"EmailBlacklisted", nil));
}
}];
}
}
#pragma mark - Username
- (void) checkIfValidUsername:(NSString*)username completion:(SignupVerifierCompletionHandler)handler
{
//is the username within the acceptable length?
if (username.length == 0)
{
[[RBXEventReporter sharedInstance] reportFormFieldValidation:RBXAFieldUsername
withContext:RBXAContextSignup
withError:RBXAErrorMissingRequiredField];
if (handler)
handler(NO, NSLocalizedString(@"UsernameMissing", nil));
return;
}
if (username.length < 3)
{
[[RBXEventReporter sharedInstance] reportFormFieldValidation:RBXAFieldUsername
withContext:RBXAContextSignup
withError:RBXAErrorTooShort];
if (handler)
handler(NO, NSLocalizedString(@"UsernameInvalidLength", nil));
return;
}
if (username.length > 20) // we limit usernames to 20 characters
{
[[RBXEventReporter sharedInstance] reportFormFieldValidation:RBXAFieldUsername
withContext:RBXAContextSignup
withError:RBXAErrorTooLong];
if (handler)
handler(NO, NSLocalizedString(@"UsernameInvalidLength", nil));
return;
}
//allow only one underscore, so long as it is not at the beginning or end of the name
//^_|\w+__|_$
NSRange underscoreLocation = [username rangeOfString:@"_"];
if (underscoreLocation.location != NSNotFound)
{
if (underscoreLocation.location == 0 || underscoreLocation.location == username.length-1)
{
if (handler)
handler(NO, NSLocalizedString(@"UsernameInvalidUnderscore", nil));
return;
}
//are there two underscores? that's not okay
NSString* afterScore = [username substringFromIndex:underscoreLocation.location+1];
NSRange otherUnderscore = [afterScore rangeOfString:@"_"];
if (otherUnderscore.length == 1)
{
if (handler)
handler(NO, NSLocalizedString(@"UsernameTooManyUnderscores", nil));
return;
}
}
//do we have any disallowed characters?
// NOTE - "_" is not picked up as a non alphanumeric character
NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:@"\\W" options:NSMatchingProgress error:nil];
NSTextCheckingResult* regexResult = [regex firstMatchInString:username options:NSMatchingCompleted range:NSMakeRange(0, username.length)];
if (regexResult)
{
if (handler)
handler(NO, NSLocalizedString(@"UsernameInvalidCharacters", nil));
return;
}
//create a callback to call once we hit the endpoint
void (^callbackBlock)(BOOL, NSString*) = ^(BOOL success, NSString *message)
{
if (success)
{
//if this endpoint fails for some reason, it will return true but with a message
//this will prevent sign-ups from being blocked by an erroneous endpoint
//if (message && message.length > 0)
//{
//an error has occurred with this endpoint, something should probably report this error
/*[[RBXEventReporter sharedInstance] reportFormFieldValidation:RBXAFieldUsername
withContext:RBXAContextSignup
withError:message];*/
//}
//looks like the user has a good name!
if (handler)
handler(YES, nil);
}
else
{
NSString* errMessage = message.length > 0 ? message : NSLocalizedString(@"UsernameCommon", nil);
//for the new endpoint...
if ([errMessage isEqualToString:@"This username is already in use"] || [errMessage isEqualToString:@"Already Taken"])
errMessage = NSLocalizedString(@"UsernameCommon", nil); //this triggers username suggestions
if (handler)
handler(NO, errMessage);
}
};
//check the server if the username is valid
if ([self usingNewSignupRules])
{
//call the new endpoint
[RobloxData checkIfValidUsername:username
completion:callbackBlock];
}
else
{
//call the old endpoint
[RobloxData oldCheckIfValidUsername:username
completion:callbackBlock];
}
}
- (void) getAlternateUsername:(NSString *)username completion:(SignupVerifierCompletionHandler)handler
{
[RobloxData recommendUsername:username
completion:^(BOOL success, NSString *newUsername)
{
if (success)
{
//Why the heck would we get back the same name back?!
if ([newUsername isEqualToString:username])
handler(NO, NSLocalizedString(@"UsernameCommon", nil));
else
handler(YES, newUsername);
}
else
//the user has a name that is taken, but we could not find a replacement
//let the user know that they need a new username
handler(NO, NSLocalizedString(@"UsernameCommon", nil));
}];
}
#pragma mark - Passwords
- (void) checkIfValidPassword:(NSString*)password withUsername:(NSString*)username completion:(SignupVerifierCompletionHandler)handler
{
if (password.length == 0)
{
handler(NO, NSLocalizedString(@"PasswordMissing", nil));
return;
}
if (password.length < 8)
{
handler(NO, NSLocalizedString([RobloxInfo thisDeviceIsATablet] ? @"PasswordWrong" : @"PasswordWrongShort", nil));
return;
}
if (password.length > 20)
{
handler(NO, NSLocalizedString([RobloxInfo thisDeviceIsATablet] ? @"PasswordWrong" : @"PasswordWrongLong", nil));
return;
}
if ([password isEqualToString:username])
{
handler(NO, NSLocalizedString(@"PasswordMatchesUsername", nil));
return;
}
//everything looks goood
//create a callback block for the password endpoint
void (^callbackBlock)(BOOL, NSString*) = ^(BOOL success, NSString *message)
{
if (success)
handler(YES, @"");
else
{
//if there is no message, then there has been a server error.
//Do not block the client's sign up if this is the case
if (!message)
handler(YES, NSLocalizedString(@"PasswordServerError", nil));
else
handler(NO, message);
}
};
//check the server if we have a valid password
if ([self usingNewSignupRules])
{
[RobloxData checkIfValidPassword:password
username:username
completion:callbackBlock];
}
else
{
[RobloxData oldCheckIfValidPassword:password
username:username
completion:callbackBlock];
}
}
- (void) checkIfPasswordsMatch:(NSString*)password withVerification:(NSString*)verify completion:(SignupVerifierCompletionHandler)handler
{
//this is a foolish implementation, but it does come with the benefit of providing a message with the success boolean
if (password.length == 0)
handler(NO, NSLocalizedString(@"PasswordMissing", nil));
else if (verify.length == 0)
handler(NO, NSLocalizedString(@"VerifyMissing", nil));
else if (![password isEqualToString:verify])
handler(NO, NSLocalizedString(@"PasswordNoMatch", nil));
else
handler(YES, @"");
}
#pragma mark - Newer / Shinier Implementation of Signup
- (NSString *)hashString:(NSString *)input
{
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
// This is an iOS5-specific method.
// It takes in the data, how much data, and then output format, which in this case is an int array.
CC_SHA256(data.bytes, data.length, digest);
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
// Parse through the CC_SHA256 results (stored inside of digest[]).
for(int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[output appendFormat:@"%02x", digest[i]];
}
return output;
}
- (void) signUpV2WithUsername:(NSString *)username
password:(NSString *)password
birthString:(NSString *)birthString
gender:(Gender)gender
completionBlock:(void (^)(NSError *signUpError))completionBlock
{
NSDate* startTime = [NSDate date];
// need to format for particular special characters
NSString *escapedPassword = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)password,
NULL,
CFSTR("!*'();:@&=+$,/?%#[]"),
kCFStringEncodingUTF8));
NSString *escapedBirthString = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)birthString,
NULL,
CFSTR("!*'();:@&=+$,/?%#[]"),
kCFStringEncodingUTF8));
NSString* formattedSignUpArgs = [NSString stringWithFormat:@"username=%@&password=%@&gender=%@&birthday=%@",
username,
escapedPassword,
gender == GENDER_GIRL ? @"female" : @"male",
escapedBirthString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[[RobloxInfo getApiBaseUrl] stringByAppendingString:@"/signup/v1/"]]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[formattedSignUpArgs dataUsingEncoding:NSUTF8StringEncoding]];
[RobloxInfo setDefaultHTTPHeadersForRequest:request];
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
bool bSignUpWithHash = iosSettings->GetValueSignUpWithHash();
if(bSignUpWithHash)
{
NSString* s;
if([RobloxInfo isTestSite])
s = [NSString stringWithFormat:@"%@%@%@%@%@%@", s6, s7, s8, s9, s10, username];
else
s = [NSString stringWithFormat:@"%@%@%@%@%@%@", s1, s2, s3, s4, s5, username];
NSString* h = [self hashString:s];
[request setValue:h forHTTPHeaderField:@"X-RBXUSER-TOKEN"];
}
[[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
RBXAnalyticsResult analyticsResult = RBXAResultFailure;
RBXAnalyticsErrorName analyticsError = RBXAErrorNoError;
if ([RBXFunctions isEmpty:error]) {
if (![RBXFunctions isEmpty:data]) {
NSDictionary *dataDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if ([RBXFunctions isEmpty:error]) {
if ([dataDict objectForKey:@"userId"])
{
analyticsResult = RBXAResultSuccess;
}
else if ([dataDict objectForKey:@"reasons"])
{
analyticsResult = RBXAResultFailure;
NSArray *reasons = [dataDict arrayForKey:@"reasons"];
NSString *primaryReason = reasons.firstObject;
if ([primaryReason.lowercaseString isEqualToString:@"BirthdayInvalid".lowercaseString]) {
analyticsError = RBXAErrorBirthdayInvalid;
} else if ([primaryReason.lowercaseString isEqualToString:@"Captcha".lowercaseString]) {
analyticsError = RBXAErrorCaptcha;
primaryReason = NSLocalizedString(@"TooManyAttempts", nil); //make sure that the captcha gets triggered
} else if ([primaryReason.lowercaseString isEqualToString:@"GenderInvalid".lowercaseString]) {
analyticsError = RBXAErrorGenderInvalid;
} else if ([primaryReason.lowercaseString isEqualToString:@"PasswordInvalid".lowercaseString]) {
analyticsError = RBXAErrorPasswordInvalid;
} else if ([primaryReason.lowercaseString isEqualToString:@"UsernameInvalid".lowercaseString]) {
analyticsError = RBXAErrorUsernameInvalidWeb;
} else if ([primaryReason.lowercaseString isEqualToString:@"UsernameTaken".lowercaseString]) {
analyticsError = RBXAErrorUsernameTaken;
}
//save the reason into the error to be displayed to the user
error = [NSError errorWithDomain:primaryReason code:httpResponse.statusCode userInfo:@{@"request":request}];
}
else
{
error = [NSError errorWithDomain:@"Response data could not be handled" code:httpResponse.statusCode userInfo:dataDict];
analyticsResult = RBXAResultUnknown;
analyticsError = RBXAErrorBadResponse;
}
}
else
{
// There was some kind of json parsing error; lets convert the data into a string and send that back to RBXHQ
NSString *unkError = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//NSLog(@"unkError: %@", unkError);
NSDictionary *originalErrorInfo = error.userInfo;
error = [NSError errorWithDomain:unkError code:httpResponse.statusCode userInfo:originalErrorInfo];
}
}
else
{
error = [NSError errorWithDomain:@"Data length is 0" code:httpResponse.statusCode userInfo:@{@"response":response}];
analyticsResult = RBXAResultFailure;
analyticsError = RBXAErrorCannotParseData;
}
}
if (nil != completionBlock) {
completionBlock(error);
}
[[SessionReporter sharedInstance] postAnalyticPayloadFromContext:RBXAContextSignup
result:analyticsResult
rbxError:analyticsError
startingTime:startTime
attemptedUsername:username
URLRequest:request
HTTPResponseCode:httpResponse ? httpResponse.statusCode : 0
responseData:data
additionalData:nil];
}] resume];
}
- (void) signUpWithUsername:(NSString *)username
password:(NSString *)password
birthString:(NSString *)birthString
gender:(Gender)gender
email:(NSString *)email
completionBlock:(void (^)(NSError *signUpError))completionBlock {
if ([LoginManager apiProxyEnabled]) {
[self signUpV2WithUsername:username password:password birthString:birthString gender:gender completionBlock:completionBlock];
return;
}
NSDate* startTime = [NSDate date];
//it is assumed that all input has been verified before calling this function
NSString* genderString = @"Unknown";
if (gender == GENDER_GIRL)
genderString = @"Female";
else if (gender == GENDER_BOY)
genderString = @"Male";
// need to format for particular special characters
NSString *escapedPassword = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)password,
NULL,
CFSTR("!*'();:@&=+$,/?%#[]"),
kCFStringEncodingUTF8));
NSString *escapedBirthString = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)birthString,
NULL,
CFSTR("!*'();:@&=+$,/?%#[]"),
kCFStringEncodingUTF8));
NSString* formattedSignUpArgs = [NSString stringWithFormat:self.signUpArgs,
username,
escapedPassword,
genderString,
escapedBirthString,
[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]];
if (email && email.length > 0)
{
NSString *escapedEmail = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)email,
NULL,
CFSTR("!*'();:@&=+$,/?%#[]"),
kCFStringEncodingUTF8));
formattedSignUpArgs = [[formattedSignUpArgs stringByAppendingString:@"&email="] stringByAppendingString:escapedEmail];
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.signUpUrlString]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[formattedSignUpArgs dataUsingEncoding:NSUTF8StringEncoding]];
[RobloxInfo setDefaultHTTPHeadersForRequest:request];
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
bool bSignUpWithHash = iosSettings->GetValueSignUpWithHash();
if(bSignUpWithHash)
{
NSString* s;
if([RobloxInfo isTestSite])
s = [NSString stringWithFormat:@"%@%@%@%@%@%@", s6, s7, s8, s9, s10, username];
else
s = [NSString stringWithFormat:@"%@%@%@%@%@%@", s1, s2, s3, s4, s5, username];
NSString* h = [self hashString:s];
[request setValue:h forHTTPHeaderField:@"X-RBXUSER-TOKEN"];
}
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:
^(NSURLResponse *response, NSData *responseData, NSError *responseError)
{
RBXAnalyticsResult analyticsResult = RBXAResultFailure;
RBXAnalyticsErrorName analyticsError = RBXAErrorNoError;
// parse the response
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSError *signupError = nil;
if(httpResponse)
{
NSDictionary* responseDict = [[NSDictionary alloc] init];
NSError* dictError = nil;
responseDict = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&dictError];
if(!dictError && responseDict)
{
NSString* statusResponse = [responseDict objectForKey:@"Status"];
if ([statusResponse isEqualToString:@"OK"] && [responseDict objectForKey:@"UserInfo"])
{
analyticsResult = RBXAResultSuccess;
}
else
{
if ([statusResponse isEqualToString:@"AccountCreationFloodcheck"])
{
signupError = [NSError errorWithDomain:NSLocalizedString(@"TooManyAttempts", nil) code:httpResponse.statusCode userInfo:responseDict];
analyticsError = RBXAErrorFloodcheckAccountCreate;
}
else
{
//might be that we haven't caught a case
//TO DO - CATCH OTHER CASES! 10/23/2015 Kyler
//server JSON formatting error
signupError = [NSError errorWithDomain:NSLocalizedString(@"JSONFormatError", nil) code:httpResponse.statusCode userInfo:responseDict];
analyticsError = RBXAErrorUnknownError;
}
}
}
else
{
//server bad response
signupError = [NSError errorWithDomain:NSLocalizedString(@"ServerBadResponse", nil) code:httpResponse.statusCode userInfo:responseDict];
analyticsError = dictError ? RBXAErrorJSONParseFailure : RBXAErrorUnknownError;
}
}
else
{
//server no response
signupError = [NSError errorWithDomain:NSLocalizedString(@"SocialGigyaErrorNotifyLoginNoResponse", nil) code:httpResponse.statusCode userInfo:nil];
analyticsError = RBXAErrorNoHTTPResponse;
}
if (nil != completionBlock)
{
completionBlock(signupError);
}
[[SessionReporter sharedInstance] postAnalyticPayloadFromContext:RBXAContextSignup
result:analyticsResult
rbxError:analyticsError
startingTime:startTime
attemptedUsername:username
URLRequest:request
HTTPResponseCode:httpResponse ? httpResponse.statusCode : 0
responseData:responseData
additionalData:nil];
}];
}
@end
+30
View File
@@ -0,0 +1,30 @@
//
// StandaloneAppStore.h
// RobloxMobile
//
// Created by Ben Tkacheff on 5/2/14.
// Copyright (c) 2014 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#include "v8tree/Instance.h"
@interface StandaloneAppStore : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
{
weak_ptr<RBX::Instance> currentPlayerPurchasing;
NSString* currentProductId;
SKProductsRequest *request;
}
-(id) init;
// WARNING: DO NOT access this directly!!!!! please use the macro 'GetStoreMgr' instead
+(StandaloneAppStore*) getStandaloneAppStore;
-(void) purchaseProduct:(NSString*)productId player:(shared_ptr<RBX::Instance>) player;
-(void) promptThirdPartyPurchase:(shared_ptr<RBX::Instance>) player productId:(std::string) productId;
-(void) signalMarketplacePurchaseFinished:(shared_ptr<RBX::Instance>) player productId:(std::string) productId receipt:(std::string) receipt purchased:(bool) purchased;
- (void)requestDidFinish:(SKRequest *)finishedRequest;
@end
+253
View File
@@ -0,0 +1,253 @@
//
// StandaloneAppStore.m
// RobloxMobile
//
// Created by Ben Tkacheff on 5/2/14.
// Copyright (c) 2014 ROBLOX. All rights reserved.
//
#import "StandaloneAppStore.h"
#import "UserInfo.h"
#include "v8datamodel/DataModel.h"
#include "v8datamodel/MarketplaceService.h"
#include "Network/Players.h"
@implementation StandaloneAppStore
- (id) init
{
if(self = [super init])
{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
+(StandaloneAppStore*) getStandaloneAppStore
{
static dispatch_once_t standaloneStorePred = 0;
__strong static StandaloneAppStore* _sharedAppStore = nil;
dispatch_once(&standaloneStorePred, ^{ // Need to use GCD for thread-safe allocation of singleton
_sharedAppStore = [[self alloc] init];
});
return _sharedAppStore;
}
-(void) resetCurrentInfo
{
currentProductId = @"";
currentPlayerPurchasing.reset();
}
-(NSString*)base64forData:(NSData*)theData
{
const uint8_t* input = (const uint8_t*)[theData bytes];
NSInteger length = [theData length];
static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t* output = (uint8_t*)data.mutableBytes;
NSInteger i;
for (i=0; i < length; i += 3) {
NSInteger value = 0;
NSInteger j;
for (j = i; j < (i + 3); j++) {
value <<= 8;
if (j < length) {
value |= (0xFF & input[j]);
}
}
NSInteger theIndex = (i / 3) * 4;
output[theIndex + 0] = table[(value >> 18) & 0x3F];
output[theIndex + 1] = table[(value >> 12) & 0x3F];
output[theIndex + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
output[theIndex + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
}
return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
}
-(NSString*) getReceiptiOS7AndAbove
{
//Get the receipt URL
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
//Check if it exists
if ([[NSFileManager defaultManager] fileExistsAtPath:receiptURL.path])
{
//Encapsulate the base64 encoded receipt on NSData
NSData *receiptData = [NSData dataWithContentsOfFile:receiptURL.path];
NSString *base64Receipt = [self base64forData:receiptData];
return base64Receipt;
}
return @"";
}
// SKPaymentTransactionObserver
- (void) paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
{
NSString* receiptString = @"";
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1)
{
NSData *receiptData = [NSData dataWithData:transaction.transactionReceipt];
receiptString = [self base64forData:receiptData];
}
#pragma GCC diagnostic pop
else
{
receiptString = [self getReceiptiOS7AndAbove];
}
[queue finishTransaction: transaction];
[self signalMarketplacePurchaseFinished:currentPlayerPurchasing.lock() productId:[currentProductId UTF8String] receipt:[receiptString UTF8String] purchased:true];
[self resetCurrentInfo];
break;
}
case SKPaymentTransactionStateFailed:
{
[queue finishTransaction: transaction];
[self signalMarketplacePurchaseFinished:currentPlayerPurchasing.lock() productId:[currentProductId UTF8String] receipt:"" purchased:false];
[self resetCurrentInfo];
break;
}
case SKPaymentTransactionStateRestored:
default:
{
break;
}
}
}
}
// SKPaymentTransactionObserver
- (void) paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads
{
// don't need anything here yet
}
// SKProductsRequestDelegate
- (void) productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
if ([self canMakePurchase])
{
NSInteger numProduct = [response.products count];
SKProduct* product = [response.products lastObject];
if (numProduct > 0 && product) // we can purchase
{
SKPayment* payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
else
{
[self signalMarketplacePurchaseFinished:currentPlayerPurchasing.lock() productId:[currentProductId UTF8String] receipt:"" purchased:false];
[self resetCurrentInfo];
}
}
else
{
[self signalMarketplacePurchaseFinished:currentPlayerPurchasing.lock() productId:[currentProductId UTF8String] receipt:"" purchased:false];
[self resetCurrentInfo];
}
}
// SKProductsRequestDelegate
- (void)request:(SKRequest *)errorRequest didFailWithError:(NSError *)error
{
NSLog(@"we have request error with %@",[error localizedDescription]);
[self signalMarketplacePurchaseFinished:currentPlayerPurchasing.lock() productId:[currentProductId UTF8String] receipt:"" purchased:false];
if (request && errorRequest == request)
{
request = nil;
}
[self resetCurrentInfo];
}
// SKProductsRequestDelegate
- (void)requestDidFinish:(SKRequest *)finishedRequest
{
if (request && finishedRequest == request)
{
request = nil;
}
}
-(void) requestProductData:(NSString* )productId
{
NSSet* set = [NSSet setWithObject:productId];
request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
request.delegate = self;
[request start];
}
- (BOOL) canMakePurchase
{
[[UserInfo CurrentPlayer] UpdatePlayerInfo];
return [SKPaymentQueue canMakePayments];
}
-(void) purchaseProduct:(NSString*)productId player:(shared_ptr<RBX::Instance>) player
{
if (![self canMakePurchase])
{
NSLog(@"Account not allowed to make purchases");
[self signalMarketplacePurchaseFinished:player productId:[productId UTF8String] receipt:"" purchased:false];
}
else if (shared_ptr<RBX::Instance> sharedPlayer = currentPlayerPurchasing.lock())
{
NSLog(@"Currently trying to purchase something, then asked to purchase another thing in the middle of it");
[self signalMarketplacePurchaseFinished:player productId:[productId UTF8String] receipt:"" purchased:false];
}
else
{
currentPlayerPurchasing = player;
currentProductId = productId;
[self requestProductData:productId];
}
}
-(void) signalMarketplacePurchaseFinished:(shared_ptr<RBX::Instance>) player productId:(std::string) productId receipt:(std::string) receipt purchased:(bool) purchased
{
if (RBX::MarketplaceService* marketService = RBX::ServiceProvider::find<RBX::MarketplaceService>(player.get()))
{
marketService->signalPromptThirdPartyPurchaseFinished(player, productId, receipt, purchased);
}
}
-(void) promptThirdPartyPurchase:(shared_ptr<RBX::Instance>) player productId:(std::string) productId
{
if (!player)
return;
if (RBX::Network::Player* thePlayer = RBX::Instance::fastDynamicCast<RBX::Network::Player>(player.get()))
{
RBX::Network::Player* localPlayer = RBX::Network::Players::findLocalPlayer(RBX::DataModel::get(player.get()));
if (localPlayer == thePlayer)
{
[self purchaseProduct:[NSString stringWithUTF8String:productId.c_str()] player:player];
}
else
{
[self signalMarketplacePurchaseFinished:player productId:productId receipt:"" purchased:false];
}
}
else
{
[self signalMarketplacePurchaseFinished:player productId:productId receipt:"" purchased:false];
}
}
@end
+39
View File
@@ -0,0 +1,39 @@
//
// Teleporter.h
// RobloxMobile
//
// Created by Ben Tkacheff on 1/30/13.
// Copyright (c) 2013 ROBLOX. All rights reserved.
//
#import "PlaceLauncher.h"
#include "v8datamodel/TeleportCallback.h"
#include "FunctionMarshaller.h"
class Teleporter: public RBX::TeleportCallback
{
PlaceLauncher* mLauncher;
RBX::FunctionMarshaller* mMarshaller;
static void teleportImpl(PlaceLauncher* launcher, std::string url, std::string ticket, std::string script)
{
NSString* urlString = [NSString stringWithCString:url.c_str() encoding:[NSString defaultCStringEncoding]];
NSString* ticketString = [NSString stringWithCString:ticket.c_str() encoding:[NSString defaultCStringEncoding]];
NSString* scriptString = [NSString stringWithCString:script.c_str() encoding:[NSString defaultCStringEncoding]];
[launcher teleport:ticketString withAuthentication:urlString withScript:scriptString];
}
public:
Teleporter(PlaceLauncher* launcher, RBX::FunctionMarshaller* marshaller): mLauncher(launcher), mMarshaller(marshaller)
{
}
virtual void doTeleport(const std::string& url, const std::string& ticket, const std::string& script)
{
mMarshaller->Submit(boost::bind(teleportImpl, mLauncher, url, ticket, script));
}
virtual bool isTeleportEnabled() const { return true; }
};
+65
View File
@@ -0,0 +1,65 @@
//
// UserInfo.h
// RobloxMobile
//
// Created by David York on 10/9/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RobloxData.h"
#import <GigyaSDK/Gigya.h>
@interface UserInfo : NSObject
@property (retain, nonatomic) RBXUserAccountNotifications* accountNotifications;
@property (retain, nonatomic) NSMutableDictionary* userSocialInfoDict;
@property (retain, nonatomic) GSResponse* userGigyaInfo;
@property (retain, nonatomic) NSNumber* userId;
@property (retain, nonatomic) NSString* username;
@property (retain, nonatomic) NSString* userEmail;
@property (retain, nonatomic) NSString* password;
@property (retain, nonatomic) NSNumber* rbxBal;
@property (retain, nonatomic) NSNumber* tikBal;
@property (retain, nonatomic) NSString* userThumbNailUrl;
@property (retain, nonatomic) NSString* bcMember;
@property (retain, nonatomic) NSString* encodedPassword;
@property (retain, nonatomic) NSString* encodedUsername;
@property (retain, nonatomic) NSString* birthday;
@property (nonatomic) BOOL userLoggedIn;
@property (nonatomic) BOOL userOver13;
@property (nonatomic) BOOL userHasSetPassword;
//Accessors
+(UserInfo*) CurrentPlayer;
-(NSString*) Robux;
-(NSString*) Tix;
-(BOOL) isConnectedToIdentity:(NSString*)identity;
-(BOOL) isConnectedToFacebook;
-(BOOL) isConnectedToTwitter;
-(BOOL) isConnectedToGooglePlus;
-(NSString*) getNameConnectedToIdentity:(NSString*)identity;
-(NSString*) GigyaUID;
-(NSString*) GigyaUIDSignature;
-(NSString*) GigyaSignatureTimestamp;
-(NSString*) GigyaName;
-(NSString*) GigyaPhotoURL;
-(NSString*) GigyaLoginProvider;
-(NSString*) GigyaLoginProviderUID;
-(NSString*) GigyaProvider;
-(NSString*) GigyaGender;
-(NSString*) GigyaBirthDay;
-(NSString*) GigyaBirthMonth;
-(NSString*) GigyaBirthYear;
-(NSString*) GigyaEmail;
//Mutators
-(void)UpdatePlayerInfo;
-(void)UpdateAccountInfo;
+(void) clearUserInfo;
@end
+257
View File
@@ -0,0 +1,257 @@
//
// UserInfo.m
// RobloxMobile
//
// Created by David York on 10/9/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#import "UserInfo.h"
#import "RobloxInfo.h"
#import "LoginManager.h"
#import "RobloxGoogleAnalytics.h"
#import "RobloxAlert.h"
#import "KeychainItemWrapper.h"
#import "RobloxData.h"
#import "NSDictionary+Parsing.h"
#import "RobloxNotifications.h"
#import "RBXFunctions.h"
#include "util/StandardOut.h"
UserInfo* _currentPlayer = nil;
NSString* convertToFriendlyString(NSNumber* original);
@implementation UserInfo
-(id)init
{
self = [super init];
if (self)
{
_userOver13 = YES;
_userLoggedIn = NO;
_userHasSetPassword = YES;
self.userSocialInfoDict = [NSMutableDictionary dictionary];
}
return self;
}
//ACCESSORS
+(UserInfo*) CurrentPlayer {
if (_currentPlayer == nil) {
_currentPlayer = [[UserInfo alloc] init];
}
return _currentPlayer;
}
-(NSString*) Robux { return convertToFriendlyString(self.rbxBal); }
-(NSString*) Tix { return convertToFriendlyString(self.tikBal);; }
-(BOOL) isConnectedToIdentity:(NSString*)identity {
if (self.userSocialInfoDict)
{
NSArray* identities = [self.userSocialInfoDict arrayForKey:@"identities" withDefault:@[]];
for (NSDictionary* idDict in identities)
{
if (idDict)
if ([[idDict stringForKey:@"provider" withDefault:@" "] isEqualToString:identity])
return YES;
}
}
return NO;
}
-(BOOL) isConnectedToFacebook { return [self isConnectedToIdentity:@"facebook"]; }
-(BOOL) isConnectedToTwitter { return [self isConnectedToIdentity:@"twitter"]; }
-(BOOL) isConnectedToGooglePlus { return [self isConnectedToIdentity:@"google"]; }
-(NSString*) getNameConnectedToIdentity:(NSString*)identity {
if (self.userSocialInfoDict)
{
NSArray* identities = [self.userSocialInfoDict arrayForKey:@"identities" withDefault:@[]];
for (NSDictionary* idDict in identities)
{
if (idDict)
if ([[idDict stringForKey:@"provider" withDefault:@" "] isEqualToString:identity])
return [idDict stringForKey:@"nickname" withDefault:nil];
}
}
return nil;
}
-(NSString*) GigyaUID { return [self.userSocialInfoDict stringForKey:@"UID" withDefault:@"null"]; }
-(NSString*) GigyaUIDSignature { return [self.userSocialInfoDict stringForKey:@"UIDSignature" withDefault:@"null"]; }
-(NSString*) GigyaSignatureTimestamp { return [self.userSocialInfoDict stringForKey:@"signatureTimestamp" withDefault:@"null"]; }
-(NSString*) GigyaName { return [self.userSocialInfoDict stringForKey:@"firstName" withDefault:@"null"]; }
-(NSString*) GigyaPhotoURL { return [self.userSocialInfoDict stringForKey:@"photoURL" withDefault:@"null"]; }
-(NSString*) GigyaLoginProvider { return [self.userSocialInfoDict stringForKey:@"loginProvider" withDefault:@"null"]; }
-(NSString*) GigyaLoginProviderUID { return [self.userSocialInfoDict stringForKey:@"loginProviderUID" withDefault:@"null"]; }
-(NSString*) GigyaProvider { return [self.userSocialInfoDict stringForKey:@"providers" withDefault:@"null"]; }
-(NSString*) GigyaGender {
NSString* gender = [self.userSocialInfoDict stringForKey:@"gender" withDefault:@"null"];
if ([gender isEqualToString:@"m"]) gender = @"MALE";
else if ([gender isEqualToString:@"f"]) gender = @"FEMALE";
else gender = @"UNKNOWN";
return gender;
}
-(NSString*) GigyaBirthDay { return [self.userSocialInfoDict stringForKey:@"birthDay" withDefault:@"null"]; }
-(NSString*) GigyaBirthMonth { return [self.userSocialInfoDict stringForKey:@"birthMonth" withDefault:@"null"]; }
-(NSString*) GigyaBirthYear { return [self.userSocialInfoDict stringForKey:@"birthYear" withDefault:@"null"]; }
-(NSString*) GigyaEmail { return [self.userSocialInfoDict stringForKey:@"email" withDefault:nil]; }
//MUTATORS
-(void) setUserLoggedIn:(BOOL)userLoggedIn {
_userLoggedIn = userLoggedIn;
if (userLoggedIn == NO)
{
//clear out the password but not the password
self.password = @"";
self.encodedPassword = @"";
self.userSocialInfoDict = nil;
//clear out the user defaults
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"password"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"LastUserLoggedIn"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"LastUserIDLoggedIn"];
//clear out the keychain item password
KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:[[[NSBundle mainBundle] bundleIdentifier] stringByAppendingString:@"RobloxLogin"] accessGroup:nil];
[keychainItem setObject:@"" forKey:(__bridge id)kSecValueData];
//notify others that we've logged out
[[NSNotificationCenter defaultCenter] postNotificationName:RBX_NOTIFY_LOGGED_OUT object:self];
}
else
{
[[NSUserDefaults standardUserDefaults] setObject:self.username forKey:@"LastUserLoggedIn"];
[[NSUserDefaults standardUserDefaults] setInteger:[self.userId integerValue]forKey:@"LastUserIDLoggedIn"];
[self UpdateAccountInfo];
}
[[NSUserDefaults standardUserDefaults] synchronize];
}
-(void)UpdatePlayerInfo
{
if (!_userLoggedIn)
return;
NSString* urlString;
urlString = [[RobloxInfo getBaseUrl] stringByAppendingString:@"mobileapi/userinfo"];
urlString = [urlString stringByReplacingOccurrencesOfString:@"http:" withString:@"https:"];
NSURL *url = [NSURL URLWithString: urlString];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60*7];
[RobloxInfo setDefaultHTTPHeadersForRequest:theRequest];
[theRequest setHTTPMethod:@"GET"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:theRequest queue:queue completionHandler:
^(NSURLResponse *response, NSData *receiptResponseData, NSError *error)
{
NSHTTPURLResponse* urlResponse = ( NSHTTPURLResponse*) response;
if(urlResponse == nil)
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR,"UserInfo: Login failed due to improper cast");
int responseStatusCode = [urlResponse statusCode];
bool bFail = false;
if (responseStatusCode == 200)
{
NSDictionary* dict = [[NSDictionary alloc] init];
NSError* error = nil;
dict = [NSJSONSerialization JSONObjectWithData:receiptResponseData options:kNilOptions error:&error];
if (error)
{
NSLog(@"UpdatePlayerInfo ERROR!! %@", error);
bFail = true;
}
/// GET THE INFO
self.userId = [dict objectForKey:@"UserID"];
self.username = [dict objectForKey:@"UserName"];
self.rbxBal = [dict objectForKey:@"RobuxBalance"];
self.tikBal = [dict objectForKey:@"TicketsBalance"];
self.userThumbNailUrl = [dict objectForKey:@"ThumbnailUrl"];
self.bcMember = [dict objectForKey:@"IsAnyBuildersClubMember"];
}
else
{
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR,"UserInfo: Cannot read from %s",[urlString cStringUsingEncoding:NSUTF8StringEncoding]);
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR,"UserInfo: Update failed with http response code: %d",responseStatusCode);
NSDictionary* httpRespHdrs = [urlResponse allHeaderFields];
NSArray* keys = [httpRespHdrs allKeys];
for (NSString* key in keys)
{
NSString* value = [httpRespHdrs valueForKey:key];
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO,"UserInfo: http header info: %s = %s", [key cStringUsingEncoding:NSUTF8StringEncoding], [value cStringUsingEncoding:NSUTF8StringEncoding]);
}
bFail = true;
}
if (bFail && _userLoggedIn)
{
// clean up
[[LoginManager sharedInstance] logoutRobloxUser];
[[NSNotificationCenter defaultCenter] postNotificationName:RBX_NOTIFY_LOGGED_OUT object:self userInfo:nil];
[RobloxGoogleAnalytics setPageViewTracking:@"Logout/Success"];
[RobloxAlert RobloxAlertWithMessage:NSLocalizedString(@"Unknown Login Failure", nil)];
}
}];
}
-(void)UpdateAccountInfo
{
#ifndef ROBLOX_DEVELOPER
[RobloxData fetchMyAccountWithCompletion:^(RBXUserAccount* account)
{
_userOver13 = account ? account.userAbove13 : NO;
self.username = account ? account.userName : nil;
self.userEmail = account ? account.email : nil;
//NSNumber* ageBracket = account ? account.ageBracket : nil;
self.userId = account ? account.userId : nil;
}];
[RobloxData fetchAccountNotificationsWithCompletion:^(RBXUserAccountNotifications *notifications) {
self.accountNotifications = notifications;
}];
[RobloxData fetchUserHasSetPasswordWithCompletion:^(bool isSet) {
self.userHasSetPassword = isSet;
}];
#endif
}
+(void) clearUserInfo
{
if (_currentPlayer != nil) {
_currentPlayer = nil;
}
}
@end
NSString* convertToFriendlyString(NSNumber* original)
{
if (original == nil)
return @"unknown";
int val = [original intValue];
if (val >= 1000000) {
return [NSString stringWithFormat:@"%d mil", val/1000000];
} else if (val >= 1000) {
int thousands = val/1000;
return [NSString stringWithFormat:@"%d,%03d", thousands, val-(thousands*1000)];
} else {
return [NSString stringWithFormat:@"%d", val];
}
}
+113
View File
@@ -0,0 +1,113 @@
//
// iOSSettingsService.cpp
// RobloxMobile
//
// Created by Ganesh Agrawal on 10/30/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#include "iOSSettingsService.h"
// Setting Maximum to 0 will allow to run on all versions up
// Setting Minimum to 0 will not allow to run any versions of that device
// The versions are based on Apple's IPSW prefix which is in the format of iPhone3,1
// http://theiphonewiki.com/wiki/index.php?title=Models
DATA_MAP_IMPL_START(iOSSettingsService)
IMPL_DATA(iPadMinimumVersion, 1);// iPad 2
IMPL_DATA(iPadMaximumVersion, 0); // to Allow all up versions
IMPL_DATA(iPhoneMinimumVersion, 1);// 4 for iPhone 4S
IMPL_DATA(iPhoneMaximumVersion, 0); // to allow all up versions
IMPL_DATA(iPodMinimumVersion, 1); // 5 for iPod 5th Gen 4" with Retina
IMPL_DATA(iPodMaximumVersion, 0); // to allow all up versions
IMPL_DATA(DisablePlayButtonForAll, false);
IMPL_DATA(DisablePlayButtonForNonBC, false);
IMPL_DATA(iPad1_MaximumIdealParts, 0);
IMPL_DATA(iPad2_MaximumIdealParts, 0);
IMPL_DATA(iPad3_MaximumIdealParts, 0);
IMPL_DATA(iPad4_MaximumIdealParts, 0);
IMPL_DATA(iPod4_MaximumIdealParts, 0);
IMPL_DATA(iPod5_MaximumIdealParts, 0);
IMPL_DATA(iPhone4s_MaximumIdealParts, 0); // References iPhone model #5 (aka iPhone 4s)
IMPL_DATA(iPhone5_MaximumIdealParts, 0); // References iPhone model #6 (aka iPhone 5)
IMPL_DATA(TimeIntervalBetweenRobuxPurchaseInMinutes, 10);// 10 Minutes
IMPL_DATA(RobloxHDTimeIntervalBetweenRobuxPurchaseInMinutes, 0);// 0 Minutes
IMPL_DATA(TimeIntervalBetweenBCPurchaseInMinutes, 60*24);// 24 Hours
IMPL_DATA(TimeIntervalBetweenCatalogPurchaseInMinutes, 10);// 10 Minutes
IMPL_DATA(TimeLimitForBillingServiceRetriesBeforeGivingUp, 48); // 48 Hours, Keep retrying with billing service for 48 hours
IMPL_DATA(AllowAppleInAppPurchase, true);
IMPL_DATA(ReadInAppPurchaseSettingsBeforeEveryPurchase, false);
// Crash Reporter
IMPL_DATA(CrashLoggingLevel, 4); // No Messages = 4, Error Only = 3, Error & Warning = 2, Error, Warning & Info = 1, All Messages = 0
IMPL_DATA(CrashlyticsPercentage, 100);
// Set by Default to Empty as we do not want to pollute either our Test account or Prod account
// Test GA Account Roblox Mobile App (Testing) use : "UA-42322750-2"
// Prod GA Account ROBLOX Mobile App (prod) use : "UA-42322750-1", This is set on client settings on www.watrbx.wtf
IMPL_DATA(iOSGoogleAnalyticsAccount2, "UA-42322750-1");
IMPL_DATA(iOSGoogleAnalyticsSampleRate, 100);
// Used to determine the endpoint for search. Set to empty string to make
// search bar disappear.
IMPL_DATA(SearchEndpointIPad, "");
IMPL_DATA(SearchEndpointIPhone, "");
IMPL_DATA(SignUpWithHash, true);
// Used to turn UIWebViewCacheManager on/off. This cache allows for uiwebviews to
// load instantaneously, but it takes around 60 mb of RAM from our app!
IMPL_DATA(CacheUIWebViews, false);
// Controls how our thumbstick behaves:
// 0 -> Stationary Classic Thumbstick
// 1 -> Thumbstick will follow thumb after a certain distance
IMPL_DATA(ThumbstickControlStyle, 1);
// FreeMemoryChecker - to exit out of place before iOS kills the app
IMPL_DATA(FreeMemoryCheckerActive, false);
IMPL_DATA(FreeMemoryCheckerRateMilliSeconds, 10000); // every 10 seconds
IMPL_DATA(FreeMemoryCheckerThresholdKiloBytes, 20480); // 20 MB free
// Memory Bouncer - forcefully grabs memory to force unloading of background apps
IMPL_DATA(MemoryBouncerActive, false);
IMPL_DATA(MemoryBouncerEnforceRateMilliSeconds, 100); // every .1 seconds
IMPL_DATA(MemoryBouncerThresholdKiloBytes, 5120); // 5 MB free
IMPL_DATA(MemoryBouncerLimitMegaBytes, 250); // 250 MB target
IMPL_DATA(MemoryBouncerLimitMegaBytesForLowMemDevices, 0); // low limit for older devices (iPod4, iPad1, etc) 0 = will disable bouncer to prevent out of memory crashes
IMPL_DATA(MemoryBouncerDelayCount, 0);
IMPL_DATA(MemoryBouncerBlockSizeKB, 0);
IMPL_DATA(MaxMemoryReporterRateMilliSeconds, 0);
IMPL_DATA(DisplayMemoryWarning, false);
IMPL_DATA(MaxLocalNotifications, 20);
IMPL_DATA(MaxLocalNotificationsPerUserID, 10);
IMPL_DATA(EnableSiteAlertBanner, true);
//Profile and Home Page Features
IMPL_DATA(EnableFriendsOnProfile, false);
//Non-Responsive Links
IMPL_DATA(EnableLinkCharacter, false);
IMPL_DATA(EnableLinkForum, false);
IMPL_DATA(EnableLinkForgottenPassword, false);
IMPL_DATA(EnableLinkTrade, false);
IMPL_DATA(EnableWebPageGameDetail, false);
//AB Tests
IMPL_DATA(EnableABTestMobileGuestMode, true);
IMPL_DATA(EnableABTestGuestFlavorText, true);
//Event Reporting
IMPL_DATA(EnableAnalyticsEventReporting, true);
IMPL_DATA(CalculateSessionReportEveryMilliSeconds, 100);
//Genres
IMPL_DATA(RBXGameGenres, "All-1|Adventure-13|Building-19|Comedy-15|Fighting-10|FPS-20|Horror-11|Medieval-8|Military-17|Naval-12|RPG-21|SciFi-9|Sports-14|Town+and+City-7|Western-16");
DATA_MAP_IMPL_END()
+100
View File
@@ -0,0 +1,100 @@
//
// iOSSettingsService.h
// RobloxMobile
//
// Created by Ganesh Agrawal on 10/30/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#ifndef __RobloxMobile__iOSSettingsService__
#define __RobloxMobile__iOSSettingsService__
#include "v8datamodel/FastLogSettings.h"
#include "util/Statistics.h"
#include <iostream>
/******************************************************************
The value are stored at iOSAppSettings
All exzample is shown with gametest3
To do it on prod replace <gametest3> with <prod>
Command to Create the Key with default value 2 for iPadMinimumVersion
SettingsConsole.exe gametest3 create iOSAppSettings.iPadMinimumVersion=2
Command to Set the Key, once created with above command
SettingsConsole.exe gametest3 get iOSAppSettings.iPadMinimumVersion=3
Command to Get all the Key
SettingsConsole.exe gametest3 get iOSAppSettings
******************************************************************/
class iOSSettingsService : public RBX::FastLogJSON
{
public:
START_DATA_MAP(iOSSettingsService)
DECLARE_DATA_INT(iPadMinimumVersion)
DECLARE_DATA_INT(iPadMaximumVersion)
DECLARE_DATA_INT(iPhoneMinimumVersion)
DECLARE_DATA_INT(iPhoneMaximumVersion)
DECLARE_DATA_INT(iPodMinimumVersion)
DECLARE_DATA_INT(iPodMaximumVersion)
DECLARE_DATA_BOOL(DisablePlayButtonForAll)
DECLARE_DATA_BOOL(DisablePlayButtonForNonBC)
DECLARE_DATA_INT(iPad1_MaximumIdealParts)
DECLARE_DATA_INT(iPad2_MaximumIdealParts)
DECLARE_DATA_INT(iPad3_MaximumIdealParts)
DECLARE_DATA_INT(iPad4_MaximumIdealParts)
DECLARE_DATA_INT(iPod4_MaximumIdealParts)
DECLARE_DATA_INT(iPod5_MaximumIdealParts)
DECLARE_DATA_INT(iPhone4s_MaximumIdealParts) // References iPhone model #5 (aka iPhone 4s)
DECLARE_DATA_INT(iPhone5_MaximumIdealParts) // References iPhone model #6 (aka iPhone 5)
DECLARE_DATA_STRING(iOSGoogleAnalyticsAccount2)
DECLARE_DATA_INT(iOSGoogleAnalyticsSampleRate)
DECLARE_DATA_INT(TimeIntervalBetweenRobuxPurchaseInMinutes)
DECLARE_DATA_INT(RobloxHDTimeIntervalBetweenRobuxPurchaseInMinutes)
DECLARE_DATA_INT(TimeIntervalBetweenBCPurchaseInMinutes)
DECLARE_DATA_INT(TimeIntervalBetweenCatalogPurchaseInMinutes)
DECLARE_DATA_INT(TimeLimitForBillingServiceRetriesBeforeGivingUp)
DECLARE_DATA_BOOL(AllowAppleInAppPurchase)
DECLARE_DATA_BOOL(ReadInAppPurchaseSettingsBeforeEveryPurchase)
DECLARE_DATA_INT(CrashLoggingLevel)
DECLARE_DATA_INT(CrashlyticsPercentage)
DECLARE_DATA_STRING(SearchEndpointIPad)
DECLARE_DATA_STRING(SearchEndpointIPhone)
DECLARE_DATA_BOOL(SignUpWithHash)
DECLARE_DATA_BOOL(CacheUIWebViews)
DECLARE_DATA_INT(ThumbstickControlStyle)
DECLARE_DATA_BOOL(FreeMemoryCheckerActive)
DECLARE_DATA_INT(FreeMemoryCheckerRateMilliSeconds)
DECLARE_DATA_INT(FreeMemoryCheckerThresholdKiloBytes)
DECLARE_DATA_BOOL(MemoryBouncerActive)
DECLARE_DATA_INT(MemoryBouncerEnforceRateMilliSeconds)
DECLARE_DATA_INT(MemoryBouncerThresholdKiloBytes)
DECLARE_DATA_INT(MemoryBouncerLimitMegaBytes)
DECLARE_DATA_INT(MemoryBouncerLimitMegaBytesForLowMemDevices)
DECLARE_DATA_INT(MemoryBouncerDelayCount)
DECLARE_DATA_INT(MemoryBouncerBlockSizeKB)
DECLARE_DATA_INT(MaxMemoryReporterRateMilliSeconds)
DECLARE_DATA_BOOL(DisplayMemoryWarning)
DECLARE_DATA_INT(MaxLocalNotifications)
DECLARE_DATA_INT(MaxLocalNotificationsPerUserID)
DECLARE_DATA_BOOL(EnableSiteAlertBanner)
DECLARE_DATA_BOOL(EnableFriendsOnProfile)
DECLARE_DATA_BOOL(EnableLinkCharacter)
DECLARE_DATA_BOOL(EnableLinkForgottenPassword)
DECLARE_DATA_BOOL(EnableLinkForum)
DECLARE_DATA_BOOL(EnableLinkTrade)
DECLARE_DATA_BOOL(EnableWebPageGameDetail)
DECLARE_DATA_BOOL(EnableABTestMobileGuestMode)
DECLARE_DATA_BOOL(EnableABTestGuestFlavorText)
DECLARE_DATA_BOOL(EnableAnalyticsEventReporting)
DECLARE_DATA_INT(CalculateSessionReportEveryMilliSeconds)
DECLARE_DATA_STRING(RBXGameGenres)
END_DATA_MAP();
};
#endif /* defined(__RobloxMobile__iOSSettingsService__) */