335 lines
10 KiB
Objective-C
335 lines
10 KiB
Objective-C
/*
|
|
* cocos2d for iPhone: http://www.cocos2d-iphone.org
|
|
*
|
|
* Copyright (c) 2008-2010 Ricardo Quesada
|
|
* Copyright (c) 2011 Zynga Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
|
|
#import "CLScoreServerPost.h"
|
|
#import "ccMacros.h"
|
|
|
|
// free function used to sort
|
|
NSInteger alphabeticSort(id string1, id string2, void *reverse);
|
|
|
|
NSInteger alphabeticSort(id string1, id string2, void *reverse)
|
|
{
|
|
if ((NSInteger *)reverse == NO)
|
|
return [string2 localizedCaseInsensitiveCompare:string1];
|
|
return [string1 localizedCaseInsensitiveCompare:string2];
|
|
}
|
|
|
|
|
|
@interface CLScoreServerPost (Private)
|
|
-(void) addValue:(NSString*)value key:(NSString*)key;
|
|
-(void) calculateHashAndAddValue:(id)value key:(NSString*)key;
|
|
-(NSString*) getHashForData;
|
|
-(NSData*) getBodyValues;
|
|
-(NSString*) encodeData:(NSString*)data;
|
|
-(NSMutableURLRequest *) scoreServerRequestWithURLString:(NSString *)url;
|
|
-(BOOL) submitScore:(NSDictionary*)dict forUpdate:(BOOL)isUpdate;
|
|
@end
|
|
|
|
|
|
@implementation CLScoreServerPost
|
|
|
|
@synthesize postStatus = postStatus_;
|
|
@synthesize ranking = ranking_;
|
|
@synthesize scoreDidUpdate = scoreDidUpdate_;
|
|
@synthesize connection = connection_;
|
|
|
|
+(id) serverWithGameName:(NSString*) name gameKey:(NSString*) key delegate:(id) delegate
|
|
{
|
|
return [[[self alloc] initWithGameName:name gameKey:key delegate:delegate] autorelease];
|
|
}
|
|
|
|
-(id) initWithGameName:(NSString*) name gameKey:(NSString*) key delegate:(id)aDelegate
|
|
{
|
|
self = [super init];
|
|
if( self ) {
|
|
gameKey = [key retain];
|
|
gameName = [name retain];
|
|
bodyValues = [[NSMutableArray arrayWithCapacity:5] retain];
|
|
delegate = [aDelegate retain];
|
|
receivedData = [[NSMutableData data] retain];
|
|
|
|
ranking_ = kServerPostInvalidRanking;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
-(void) dealloc
|
|
{
|
|
CCLOGINFO(@"deallocing %@", self);
|
|
[delegate release];
|
|
[gameKey release];
|
|
[gameName release];
|
|
[bodyValues release];
|
|
[receivedData release];
|
|
[connection_ release];
|
|
[super dealloc];
|
|
}
|
|
|
|
|
|
#pragma mark ScoreServer send scores
|
|
-(BOOL) sendScore: (NSDictionary*) dict
|
|
{
|
|
return [self submitScore:dict forUpdate:NO];
|
|
}
|
|
|
|
-(BOOL) updateScore: (NSDictionary*) dict
|
|
{
|
|
if (![dict objectForKey:@"cc_playername"]) {
|
|
// fail. cc_playername + cc_device_id are needed to update an score
|
|
[NSException raise:@"cocosLive:updateScore" format:@"cc_playername not found"];
|
|
}
|
|
return [self submitScore:dict forUpdate:YES];
|
|
}
|
|
|
|
-(BOOL) submitScore: (NSDictionary*)dict forUpdate:(BOOL)isUpdate
|
|
{
|
|
[receivedData setLength:0];
|
|
[bodyValues removeAllObjects];
|
|
|
|
// reset status
|
|
postStatus_ = kPostStatusOK;
|
|
|
|
// create the request
|
|
NSMutableURLRequest *post = [self scoreServerRequestWithURLString:(isUpdate ? SCORE_SERVER_UPDATE_URL : SCORE_SERVER_SEND_URL)];
|
|
|
|
CC_MD5_Init( &md5Ctx);
|
|
|
|
// hash SHALL be calculated in certain order
|
|
NSArray *keys = [dict allKeys];
|
|
int reverseSort = NO;
|
|
NSArray *sortedKeys = [keys sortedArrayUsingFunction:alphabeticSort context:&reverseSort];
|
|
for( id key in sortedKeys )
|
|
[self calculateHashAndAddValue:[dict objectForKey:key] key:key];
|
|
|
|
// device id is hashed to prevent spoofing this same score from different devices
|
|
// one way to prevent a replay attack is to send cc_id & cc_time and use it as primary keys
|
|
|
|
[self addValue:[[UIDevice currentDevice] uniqueIdentifier] key:@"cc_device_id"];
|
|
[self addValue:gameName key:@"cc_gamename"];
|
|
[self addValue:[self getHashForData] key:@"cc_hash"];
|
|
[self addValue:SCORE_SERVER_PROTOCOL_VERSION key:@"cc_prot_ver"];
|
|
|
|
[post setHTTPBody: [self getBodyValues] ];
|
|
|
|
// create the connection with the request
|
|
// and start loading the data
|
|
self.connection=[NSURLConnection connectionWithRequest:post delegate:self];
|
|
|
|
if ( ! connection_)
|
|
return NO;
|
|
|
|
return YES;
|
|
}
|
|
|
|
-(NSMutableURLRequest *) scoreServerRequestWithURLString:(NSString *)url {
|
|
NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:[NSURL URLWithString: url]
|
|
cachePolicy:NSURLRequestUseProtocolCachePolicy
|
|
timeoutInterval:10.0];
|
|
|
|
[request setHTTPMethod: @"POST"];
|
|
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
|
|
return request;
|
|
}
|
|
|
|
-(void) calculateHashAndAddValue:(id) value key:(NSString*) key
|
|
{
|
|
NSString *val;
|
|
// value shall be a string or nsnumber
|
|
if( [value respondsToSelector:@selector(stringValue)] )
|
|
val = [value stringValue];
|
|
else if( [value isKindOfClass:[NSString class]] )
|
|
val = value;
|
|
else
|
|
[NSException raise:@"Invalid format for value" format:@"Invalid format for value. addValue"];
|
|
|
|
[self addValue:val key:key];
|
|
|
|
const char * data = [val UTF8String];
|
|
CC_MD5_Update( &md5Ctx, data, strlen(data) );
|
|
}
|
|
|
|
-(void) addValue:(NSString*)value key:(NSString*) key
|
|
{
|
|
|
|
NSString *encodedValue = [self encodeData:value];
|
|
NSString *encodedKey = [self encodeData:key];
|
|
|
|
[bodyValues addObject: [NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue] ];
|
|
}
|
|
|
|
-(NSData*) getBodyValues {
|
|
NSMutableData *data = [[NSMutableData alloc] init];
|
|
|
|
BOOL first=YES;
|
|
for( NSString *s in bodyValues ) {
|
|
if( !first)
|
|
[data appendBytes:"&" length:1];
|
|
|
|
[data appendBytes:[s UTF8String] length:[s length]];
|
|
first = NO;
|
|
}
|
|
|
|
return [data autorelease];
|
|
}
|
|
|
|
-(NSString*) getHashForData
|
|
{
|
|
NSString *ret;
|
|
unsigned char pTempKey[16];
|
|
|
|
// update the hash with the secret key
|
|
const char *data = [gameKey UTF8String];
|
|
CC_MD5_Update(&md5Ctx, data, strlen(data));
|
|
|
|
// then get the hash
|
|
CC_MD5_Final( pTempKey, &md5Ctx);
|
|
|
|
// NSData *nsdata = [NSData dataWithBytes:pTempKey length:16];
|
|
ret = [NSString stringWithString:@""];
|
|
for( int i=0;i<16;i++) {
|
|
ret = [NSString stringWithFormat:@"%@%02x", ret, pTempKey[i] ];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
-(NSString*) encodeData:(NSString*) data
|
|
{
|
|
NSString *newData;
|
|
|
|
newData = [data stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
// '&' and '=' should be encoded manually
|
|
newData = [newData stringByReplacingOccurrencesOfString:@"&" withString:@"%26"];
|
|
newData = [newData stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"];
|
|
|
|
return newData;
|
|
}
|
|
|
|
#pragma mark NSURLConnection Delegate
|
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
|
|
{
|
|
// this method is called when the server has determined that it
|
|
// has enough information to create the NSURLResponse
|
|
|
|
// it can be called multiple times, for example in the case of a
|
|
// redirect, so each time we reset the data.
|
|
// receivedData is declared as a method instance elsewhere
|
|
[receivedData setLength:0];
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
|
|
{
|
|
// append the new data to the receivedData
|
|
// receivedData is declared as a method instance elsewhere
|
|
[receivedData appendData:data];
|
|
|
|
// NSString *dataString = [NSString stringWithCString:[data bytes] length: [data length]];
|
|
// CCLOG( @"data: %@", dataString);
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
|
|
{
|
|
CCLOG(@"Connection failed");
|
|
|
|
// wifi problems ?
|
|
postStatus_ = kPostStatusConnectionFailed;
|
|
|
|
// release the connection
|
|
self.connection = nil;
|
|
|
|
if( [delegate respondsToSelector:@selector(scorePostFail:) ] )
|
|
[delegate scorePostFail:self];
|
|
}
|
|
|
|
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
|
|
{
|
|
// release the connection
|
|
self.connection = nil;
|
|
|
|
// NSString *dataString = [NSString stringWithCString:[receivedData bytes] length: [receivedData length]];
|
|
NSString *dataString = [NSString stringWithCString:[receivedData bytes] encoding: NSASCIIStringEncoding];
|
|
|
|
if( [dataString hasPrefix:@"OK:"] ) {
|
|
// parse ranking and other possible answers
|
|
NSArray *values = [dataString componentsSeparatedByString:@":"];
|
|
NSArray *answer = [ [values objectAtIndex:1] componentsSeparatedByString:@","];
|
|
NSEnumerator *nse = [answer objectEnumerator];
|
|
|
|
// Create a holder for each line we are going to work with
|
|
NSString *line;
|
|
|
|
// Loop through all the lines in the lines array processing each one
|
|
while( (line = [nse nextObject]) ) {
|
|
NSArray *keyvalue = [line componentsSeparatedByString:@"="];
|
|
// NSLog(@"%@",keyvalue);
|
|
if( [[keyvalue objectAtIndex:0] isEqual:@"ranking"] ) {
|
|
ranking_ = [[keyvalue objectAtIndex:1] intValue];
|
|
} else if( [[keyvalue objectAtIndex:0] isEqual:@"score_updated"] ) {
|
|
scoreDidUpdate_ = [[keyvalue objectAtIndex:1] boolValue];
|
|
}
|
|
|
|
}
|
|
if( [delegate respondsToSelector:@selector(scorePostOk:) ] )
|
|
[delegate scorePostOk:self];
|
|
|
|
} else if( [dataString hasPrefix: @"OK"] ) {
|
|
|
|
// Ok
|
|
postStatus_ = kPostStatusOK;
|
|
|
|
if( [delegate respondsToSelector:@selector(scorePostOk:) ] )
|
|
[delegate scorePostOk:self];
|
|
|
|
|
|
} else {
|
|
|
|
NSLog(@"cocoslive: Post Score failed. Reason: %@", dataString);
|
|
|
|
// Error parsing answer
|
|
postStatus_ = kPostStatusPostFailed;
|
|
|
|
if( [delegate respondsToSelector:@selector(scorePostFail:) ] )
|
|
[delegate scorePostFail:self];
|
|
}
|
|
}
|
|
|
|
-(NSURLRequest *)connection:(NSURLConnection *)connection
|
|
willSendRequest:(NSURLRequest *)request
|
|
redirectResponse:(NSURLResponse *)redirectResponse
|
|
{
|
|
NSURLRequest *newRequest=request;
|
|
if (redirectResponse) {
|
|
newRequest=nil;
|
|
}
|
|
|
|
return newRequest;
|
|
}
|
|
|
|
@end
|