IJMinecraftLevel.m 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. //
  2. // IJMinecraftLevel.m
  3. // InsideJob
  4. //
  5. // Created by Adam Preble on 10/7/10.
  6. // Copyright 2010 Adam Preble. All rights reserved.
  7. //
  8. #import "IJMinecraftLevel.h"
  9. #import "IJInventoryItem.h"
  10. @implementation IJMinecraftLevel
  11. - (NBTContainer *)containerWithName:(NSString *)theName inArray:(NSArray *)array
  12. {
  13. for (NBTContainer *container in array)
  14. {
  15. if ([container.name isEqual:theName])
  16. return container;
  17. }
  18. return nil;
  19. }
  20. - (NBTContainer *)inventoryList
  21. {
  22. // Inventory is found in:
  23. // - compound "Data"
  24. // - compound "Player"
  25. // - list "Inventory"
  26. // *
  27. NBTContainer *dataCompound = [self childNamed:@"Data"];
  28. NBTContainer *playerCompound = [dataCompound childNamed:@"Player"];
  29. NBTContainer *inventoryList = [playerCompound childNamed:@"Inventory"];
  30. // TODO: Check for error conditions here.
  31. return inventoryList;
  32. }
  33. - (NSArray *)inventory
  34. {
  35. NSMutableArray *output = [NSMutableArray array];
  36. for (NSArray *listItems in [self inventoryList].children)
  37. {
  38. IJInventoryItem *invItem = [[IJInventoryItem alloc] init];
  39. invItem.itemId = [[self containerWithName:@"id" inArray:listItems].numberValue shortValue];
  40. invItem.count = [[self containerWithName:@"Count" inArray:listItems].numberValue unsignedCharValue];
  41. invItem.damage = [[self containerWithName:@"Damage" inArray:listItems].numberValue shortValue];
  42. invItem.slot = [[self containerWithName:@"Slot" inArray:listItems].numberValue unsignedCharValue];
  43. [output addObject:invItem];
  44. [invItem release];
  45. }
  46. return output;
  47. }
  48. - (void)setInventory:(NSArray *)newInventory
  49. {
  50. NSMutableArray *newChildren = [NSMutableArray array];
  51. NBTContainer *inventoryList = [self inventoryList];
  52. if (inventoryList.listType != NBTTypeCompound)
  53. {
  54. // There appears to be a bug in the way Minecraft writes empty inventory lists; it appears to
  55. // set the list type to 'byte', so we will correct it here.
  56. NSLog(@"%s Fixing inventory list type; was %d.", __PRETTY_FUNCTION__, inventoryList.listType);
  57. inventoryList.listType = NBTTypeCompound;
  58. }
  59. for (IJInventoryItem *invItem in newInventory)
  60. {
  61. NSArray *listItems = [NSArray arrayWithObjects:
  62. [NBTContainer containerWithName:@"id" type:NBTTypeShort numberValue:[NSNumber numberWithShort:invItem.itemId]],
  63. [NBTContainer containerWithName:@"Damage" type:NBTTypeShort numberValue:[NSNumber numberWithShort:invItem.damage]],
  64. [NBTContainer containerWithName:@"Count" type:NBTTypeByte numberValue:[NSNumber numberWithShort:invItem.count]],
  65. [NBTContainer containerWithName:@"Slot" type:NBTTypeByte numberValue:[NSNumber numberWithShort:invItem.slot]],
  66. nil];
  67. [newChildren addObject:listItems];
  68. }
  69. inventoryList.children = newChildren;
  70. }
  71. - (NBTContainer *)worldTimeContainer
  72. {
  73. return [[self childNamed:@"Data"] childNamed:@"Time"];
  74. }
  75. #pragma mark -
  76. #pragma mark Helpers
  77. + (NSString *)pathForWorldAtIndex:(int)worldIndex
  78. {
  79. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
  80. NSString *path = [paths objectAtIndex:0];
  81. path = [path stringByAppendingPathComponent:@"minecraft"];
  82. path = [path stringByAppendingPathComponent:@"saves"];
  83. path = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"World%d", worldIndex]];
  84. return path;
  85. }
  86. + (NSString *)pathForLevelDatAtIndex:(int)worldIndex
  87. {
  88. return [[[self class] pathForWorldAtIndex:worldIndex] stringByAppendingPathComponent:@"level.dat"];
  89. }
  90. + (NSString *)pathForSessionLockAtIndex:(int)worldIndex
  91. {
  92. return [[[self class] pathForWorldAtIndex:worldIndex] stringByAppendingPathComponent:@"session.lock"];
  93. }
  94. + (BOOL)worldExistsAtIndex:(int)worldIndex
  95. {
  96. return [[NSFileManager defaultManager] fileExistsAtPath:[[self class] pathForLevelDatAtIndex:worldIndex]];
  97. }
  98. + (NSData *)dataWithInt64:(int64_t)v
  99. {
  100. NSMutableData *data = [NSMutableData data];
  101. uint32_t v0 = htonl(v >> 32);
  102. uint32_t v1 = htonl(v);
  103. [data appendBytes:&v0 length:4];
  104. [data appendBytes:&v1 length:4];
  105. return data;
  106. }
  107. + (int64_t)int64FromData:(NSData *)data
  108. {
  109. uint8_t *bytes = (uint8_t *)[data bytes];
  110. uint64_t n = ntohl(*((uint32_t *)(bytes + 0)));
  111. n <<= 32;
  112. n += ntohl(*((uint32_t *)(bytes + 4)));
  113. return n;
  114. }
  115. + (int64_t)writeToSessionLockAtIndex:(int)worldIndex
  116. {
  117. NSString *path = [IJMinecraftLevel pathForSessionLockAtIndex:worldIndex];
  118. NSDate *now = [NSDate date];
  119. NSTimeInterval interval = [now timeIntervalSince1970];
  120. int64_t milliseconds = (int64_t)(interval * 1000.0);
  121. // write as number of milliseconds
  122. NSData *data = [IJMinecraftLevel dataWithInt64:milliseconds];
  123. [data writeToFile:path atomically:YES];
  124. return milliseconds;
  125. }
  126. + (BOOL)checkSessionLockAtIndex:(int)worldIndex value:(int64_t)checkValue
  127. {
  128. NSString *path = [IJMinecraftLevel pathForSessionLockAtIndex:worldIndex];
  129. NSData *data = [NSData dataWithContentsOfFile:path];
  130. if (!data)
  131. {
  132. NSLog(@"Failed to read session lock at %@", path);
  133. return NO;
  134. }
  135. int64_t milliseconds = [IJMinecraftLevel int64FromData:data];
  136. return checkValue == milliseconds;
  137. }
  138. @end