mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
GEEKING
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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==×tamp=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=%@×tamp=%@"
|
||||
, [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=%@×tamp=%@"
|
||||
, [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
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
*/
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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; }
|
||||
};
|
||||
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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__) */
|
||||
Reference in New Issue
Block a user