insidejob/Classes/IJMinecraftLevel.m
Nick Loose 9345b2cc7b Can now load named worlds from beta 1.3
Used Godzil's 3ecd8a418f95e73c60890e13e5e09614234ea1df to load from folder and added the ability to save worlds and list all worlds in minecraft/saves/
2011-03-10 19:39:20 +01:00

165 lines
5.0 KiB
Objective-C

//
// 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"
// *
NBTContainer *dataCompound = [self childNamed:@"Data"];
NBTContainer *playerCompound = [dataCompound childNamed:@"Player"];
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"];
}
+ (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