// // IJMinecraftLevel.m // InsideJob // // Created by Adam Preble on 10/7/10. // Copyright 2010 Adam Preble. All rights reserved. // Changes for opening folder Copyright 2011 Manoel Trapier // #import "IJMinecraftLevel.h" #import "IJInventoryItem.h" @implementation IJMinecraftLevel - (NBTContainer *)containerWithName:(NSString *)theName inArray:(NSArray *)array { for (NBTContainer *container in array) { if ([container.name isEqual:theName]) return container; } return nil; } - (NBTContainer *)inventoryList { // Inventory is found in: // - compound "Data" // - compound "Player" // - list "Inventory" // * // SMP Player have not the same structure, there is no "DATA" compound, nor "player" NBTContainer *playerCompound; NBTContainer *dataCompound = [self childNamed:@"Data"]; if (dataCompound != nil) { playerCompound = [dataCompound childNamed:@"Player"]; } else { NSLog(@"Player file is from a SMP file, not level.dat one"); playerCompound = self; } NBTContainer *inventoryList = [playerCompound childNamed:@"Inventory"]; // TODO: Check for error conditions here. return inventoryList; } - (NSArray *)inventory { NSMutableArray *output = [NSMutableArray array]; for (NSArray *listItems in [self inventoryList].children) { IJInventoryItem *invItem = [[IJInventoryItem alloc] init]; invItem.itemId = [[self containerWithName:@"id" inArray:listItems].numberValue shortValue]; invItem.count = [[self containerWithName:@"Count" inArray:listItems].numberValue unsignedCharValue]; invItem.damage = [[self containerWithName:@"Damage" inArray:listItems].numberValue shortValue]; invItem.slot = [[self containerWithName:@"Slot" inArray:listItems].numberValue unsignedCharValue]; [output addObject:invItem]; [invItem release]; } return output; } - (void)setInventory:(NSArray *)newInventory { NSMutableArray *newChildren = [NSMutableArray array]; NBTContainer *inventoryList = [self inventoryList]; if (inventoryList.listType != NBTTypeCompound) { // There appears to be a bug in the way Minecraft writes empty inventory lists; it appears to // set the list type to 'byte', so we will correct it here. NSLog(@"%s Fixing inventory list type; was %d.", __PRETTY_FUNCTION__, inventoryList.listType); inventoryList.listType = NBTTypeCompound; } for (IJInventoryItem *invItem in newInventory) { NSArray *listItems = [NSArray arrayWithObjects: [NBTContainer containerWithName:@"id" type:NBTTypeShort numberValue:[NSNumber numberWithShort:invItem.itemId]], [NBTContainer containerWithName:@"Damage" type:NBTTypeShort numberValue:[NSNumber numberWithShort:invItem.damage]], [NBTContainer containerWithName:@"Count" type:NBTTypeByte numberValue:[NSNumber numberWithShort:invItem.count]], [NBTContainer containerWithName:@"Slot" type:NBTTypeByte numberValue:[NSNumber numberWithShort:invItem.slot]], nil]; [newChildren addObject:listItems]; } inventoryList.children = newChildren; } - (NBTContainer *)worldTimeContainer { return [[self childNamed:@"Data"] childNamed:@"Time"]; } #pragma mark - #pragma mark Helpers + (NSString *)pathForWorldAtIndex:(int)worldIndex { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); NSString *path = [paths objectAtIndex:0]; path = [path stringByAppendingPathComponent:@"minecraft"]; path = [path stringByAppendingPathComponent:@"saves"]; path = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"World%d", worldIndex]]; return path; } + (NSData *)dataWithInt64:(int64_t)v { NSMutableData *data = [NSMutableData data]; uint32_t v0 = htonl(v >> 32); uint32_t v1 = htonl(v); [data appendBytes:&v0 length:4]; [data appendBytes:&v1 length:4]; return data; } + (int64_t)int64FromData:(NSData *)data { uint8_t *bytes = (uint8_t *)[data bytes]; uint64_t n = ntohl(*((uint32_t *)(bytes + 0))); n <<= 32; n += ntohl(*((uint32_t *)(bytes + 4))); return n; } /******************************************************************************/ + (NSString *)pathForLevelDatAtFolder:(NSString *)worldPath { return [worldPath stringByAppendingPathComponent:@"level.dat"]; } + (NSString *)pathForSessionLockAtFolder:(NSString *)worldPath { return [worldPath stringByAppendingPathComponent:@"session.lock"]; } + (NSString *)pathForPlayer:(NSString *)loadedPlayer withWorld:(NSString *)worldPath; { /* loadedPlayer == nil, we use level.dat, or else we use the name */ if (loadedPlayer == nil) return [self pathForLevelDatAtFolder:worldPath]; return [[[worldPath stringByAppendingPathComponent:@"players"] stringByAppendingPathComponent:loadedPlayer] stringByAppendingPathExtension:@".dat"]; } + (BOOL)worldExistsAtFolder:(NSString *)worldPath { return [[NSFileManager defaultManager] fileExistsAtPath:[[self class] pathForLevelDatAtFolder:worldPath]]; } + (int64_t)writeToSessionLockAtFolder:(NSString *)worldPath { NSString *path = [IJMinecraftLevel pathForSessionLockAtFolder:worldPath]; NSDate *now = [NSDate date]; NSTimeInterval interval = [now timeIntervalSince1970]; int64_t milliseconds = (int64_t)(interval * 1000.0); // write as number of milliseconds NSData *data = [IJMinecraftLevel dataWithInt64:milliseconds]; [data writeToFile:path atomically:YES]; return milliseconds; } + (BOOL)checkSessionLockAtFolder:(NSString *)worldPath value:(int64_t)checkValue { NSString *path = [IJMinecraftLevel pathForSessionLockAtFolder:worldPath]; NSData *data = [NSData dataWithContentsOfFile:path]; if (!data) { NSLog(@"Failed to read session lock at %@", path); return NO; } int64_t milliseconds = [IJMinecraftLevel int64FromData:data]; return checkValue == milliseconds; } @end