def loadMetadata(self): try: metadataTag = nbt.load( buf=self.selectedRevision.readFile("level.dat")) self.metadata = AnvilWorldMetadata(metadataTag) self.loadBlockMapping() except (EnvironmentError, zlib.error, NBTFormatError) as e: log.info( "Error loading level.dat, trying level.dat_old ({0})".format( e)) try: metadataTag = nbt.load( buf=self.selectedRevision.readFile("level.dat_old")) self.metadata = AnvilWorldMetadata(metadataTag) self.metadata.dirty = True log.info("level.dat restored from backup.") except Exception as e: traceback.print_exc() log.info( "%r while loading level.dat_old. Initializing with defaults.", e) self._createMetadataTag() if self.metadata.version != VERSION_ANVIL: raise LevelFormatError( "Pre-Anvil world formats are not supported (for now)")
def getPlayerTag(self, playerUUID=""): """ Return the root NBT tag for the named player. Raise PlayerNotFound if not present. Parameters ---------- playerUUID : unicode The player ID returned from :ref:`listPlayers` Returns ------- player : AnvilPlayerRef """ if playerUUID == "": if "Player" in self.metadata.rootTag: # single-player world playerTag = self.metadata.rootTag["Player"] return playerTag raise PlayerNotFound(playerUUID) else: playerFilePath = "playerdata/%s.dat" % playerUUID if self.selectedRevision.containsFile(playerFilePath): # multiplayer world, found this player playerTag = nbt.load( buf=self.selectedRevision.readFile(playerFilePath)) return playerTag else: raise PlayerNotFound(playerUUID)
def __init__(self, filename, create=False): raise NotImplementedError("No adapter for zipped world/schematic files yet!!!") self.zipfilename = filename tempdir = tempfile.mktemp("schematic") if create is False: zf = zipfile.ZipFile(filename) zf.extractall(tempdir) zf.close() super(ZipSchematic, self).__init__(tempdir, create) atexit.register(shutil.rmtree, self.worldFolder.filename, True) try: schematicDat = nbt.load(self.worldFolder.getFilePath("schematic.dat")) self.Width = schematicDat['Width'].value self.Height = schematicDat['Height'].value self.Length = schematicDat['Length'].value if "Materials" in schematicDat: self.blocktypes = blocktypes_named[schematicDat["Materials"].value] except Exception as e: print "Exception reading schematic.dat, skipping: {0!r}".format(e) self.Width = 0 self.Length = 0
def testErrors(self): """ attempt to name elements of a TAG_List named list elements are not allowed by the NBT spec, so we must discard any names when writing a list. """ level = self.testCreate() level["Map"]["Spawn"][0].name = "Torg Potter" data = level.save() newlevel = nbt.load(buf=data) n = newlevel["Map"]["Spawn"][0].name if n: print "Named list element failed: %s" % n # attempt to delete non-existent TAG_Compound elements # this generates a KeyError like a python dict does. level = self.testCreate() try: del level["DEADBEEF"] except KeyError: pass else: assert False
def isLevel(cls, filename): """ Return True if the given level adapter can load the given filename, False otherwise. Tries to call cls.canOpenFile on the filename, then cls._isDataLevel on the file's data, then cls._isTagLevel on an NBT tree loaded from that data. If none of these methods are present, return False. Subclasses should implement one of canOpenFile, _isDataLevel, or _isTagLevel. """ if hasattr(cls, "canOpenFile"): return cls.canOpenFile(filename) if os.path.isfile(filename): with open(filename, "rb") as f: data = f.read() if hasattr(cls, "_isDataLevel"): return cls._isDataLevel(data) if hasattr(cls, "_isTagLevel"): try: rootTag = nbt.load(filename, data) except: return False return cls._isTagLevel(rootTag) return False
def __init__(self, filename, create=False): raise NotImplementedError( "No adapter for zipped world/schematic files yet!!!") self.zipfilename = filename tempdir = tempfile.mktemp("schematic") if create is False: zf = zipfile.ZipFile(filename) zf.extractall(tempdir) zf.close() super(ZipSchematic, self).__init__(tempdir, create) atexit.register(shutil.rmtree, self.worldFolder.filename, True) try: schematicDat = nbt.load( self.worldFolder.getFilePath("schematic.dat")) self.Width = schematicDat['Width'].value self.Height = schematicDat['Height'].value self.Length = schematicDat['Length'].value if "Materials" in schematicDat: self.blocktypes = blocktypes_named[ schematicDat["Materials"].value] except Exception as e: print "Exception reading schematic.dat, skipping: {0!r}".format(e) self.Width = 0 self.Length = 0
def readChunk(self, cx, cz, dimName): """ Return chunk (cx, cz) in the given dimension as an AnvilChunkData. Raise ChunkNotPresent if not found. :type cx: int or dtype :type cz: int or dtype :type dimName: str :return: :rtype: AnvilChunkData """ try: data = self.selectedRevision.readChunkBytes(cx, cz, dimName) chunkTag = nbt.load(buf=data) log.debug("_getChunkData: Chunk %s loaded (%s bytes)", (cx, cz), len(data)) chunkData = AnvilChunkData(self, cx, cz, dimName, chunkTag) except ChunkNotPresent: raise except ( KeyError, IndexError, zlib.error, UnicodeError ) as e: # Missing nbt keys, lists too short, decompression failure, unknown NBT tags raise AnvilChunkFormatError("Error loading chunk: %r" % e, None, sys.exc_info()[2]) return chunkData
def repair(self): """ Fix the following problems with the region file: - remove offset table entries pointing past the end of the file - remove entries that overlap other entries - relocate offsets for chunks whose xPos,yPos don't match """ lostAndFound = {} _freeSectors = [True] * len(self.freeSectors) _freeSectors[0] = _freeSectors[1] = False deleted = 0 recovered = 0 log.info("Beginning repairs on {file} ({chunks} chunks)".format(file=os.path.basename(self.path), chunks=sum(self.offsets > 0))) for index, offset in enumerate(self.offsets): if offset: cx = index & 0x1f cz = index >> 5 sectorStart = offset >> 8 sectorCount = offset & 0xff try: if sectorStart + sectorCount > len(self.freeSectors): raise RegionFormatError("Offset {start}:{end} ({offset}) at index {index} pointed outside of " "the file".format(start=sectorStart, end=sectorStart + sectorCount, index=index, offset=offset)) data = self.readChunkBytes(cx, cz) chunkTag = nbt.load(buf=data) lev = chunkTag["Level"] xPos = lev["xPos"].value & 0x1f zPos = lev["zPos"].value & 0x1f overlaps = False for i in xrange(sectorStart, sectorStart + sectorCount): if _freeSectors[i] is False: overlaps = True _freeSectors[i] = False if xPos != cx or zPos != cz or overlaps: lostAndFound[xPos, zPos] = data if (xPos, zPos) != (cx, cz): raise RegionFormatError("Chunk {found} was found in the slot reserved for {expected}".format(found=(xPos, zPos), expected=(cx, cz))) else: raise RegionFormatError("Chunk {found} (in slot {expected}) has overlapping sectors with another chunk!".format(found=(xPos, zPos), expected=(cx, cz))) except Exception as e: log.info("Unexpected chunk data at sector {sector} ({exc})".format(sector=sectorStart, exc=e)) self._setOffset(cx, cz, 0) deleted += 1 for cPos, foundData in lostAndFound.iteritems(): cx, cz = cPos if self._getOffset(cx, cz) == 0: log.info("Found chunk {found} and its slot is empty, recovering it".format(found=cPos)) self.writeChunk(cx, cz, foundData) recovered += 1 log.info("Repair complete. Removed {0} chunks, recovered {1} chunks, net {2}".format(deleted, recovered, recovered - deleted))
def getMapTag(self, mapID): mapPath = "data/map_%s.dat" % mapID if not self.selectedRevision.containsFile(mapPath): raise KeyError("Map %s not found" % mapID) mapData = self.selectedRevision.readFile(mapPath) mapNBT = nbt.load(buf=mapData) return mapNBT
def getMapTag(self, mapID): mapPath = self._getMapPath(mapID) if not self.selectedRevision.containsFile(mapPath): raise KeyError("Map %s not found" % mapID) mapData = self.selectedRevision.readFile(mapPath) mapNBT = nbt.load(buf=mapData) return mapNBT
def loadMetadata(self): try: metadataTag = nbt.load(buf=self.selectedRevision.readFile("level.dat")) self.metadata = AnvilWorldMetadata(metadataTag) self.loadBlockMapping() except (EnvironmentError, zlib.error, NBTFormatError) as e: log.info("Error loading level.dat, trying level.dat_old ({0})".format(e)) try: metadataTag = nbt.load(buf=self.selectedRevision.readFile("level.dat_old")) self.metadata = AnvilWorldMetadata(metadataTag) self.metadata.dirty = True log.info("level.dat restored from backup.") except Exception as e: traceback.print_exc() log.info("%r while loading level.dat_old. Initializing with defaults.", e) self._createMetadataTag() assert self.metadata.version == VERSION_ANVIL, "Pre-Anvil world formats are not supported (for now)"
def getWorldInfo(cls, filename, displayNameLimit=40): try: if os.path.isdir(filename): folderName = os.path.basename(filename) levelDat = os.path.join(filename, "level.dat") else: folderName = os.path.basename(os.path.dirname(filename)) levelDat = filename levelTag = nbt.load(levelDat) try: displayName = levelTag['Data']['LevelName'].value if len(displayName) > displayNameLimit: displayName = displayName[:displayNameLimit] + "..." if len(folderName) > displayNameLimit: folderName = folderName[:displayNameLimit] + "..." if folderName != displayName: displayName = "%s (%s)" % (displayName, folderName) except Exception as e: log.warn("Failed to get display name for level.", exc_info=1) displayName = folderName try: lastPlayedTime = levelTag['Data']['LastPlayed'].value except Exception as e: log.warn("Failed to get last-played time for level.", exc_info=1) lastPlayedTime = 0 version = "Unknown Version" try: metadata = AnvilWorldMetadata(levelTag) stackVersion = VERSION_1_8 if metadata.is1_8World( ) else VERSION_1_7 if stackVersion == VERSION_1_7: version = "Minecraft 1.7" if "FML" in metadata.metadataTag: version = "MinecraftForge 1.7" if stackVersion == VERSION_1_8: version = "Minecraft 1.8" except Exception as e: log.warn("Failed to get version info for %s: %r", filename, e, exc_info=1) return WorldInfo(displayName, lastPlayedTime, version) except Exception as e: log.error("Failed getting world info for %s: %r", filename, e) return WorldInfo(str(e), 0, "")
def testBigEndianIntHeightMap(): """ Test modifying, saving, and loading the new TAG_Int_Array heightmap added with the Anvil format. """ region = RegionFile(TempFile("AnvilWorld/region/r.0.0.mca")) chunk_data = region.readChunkBytes(0, 0) chunk = nbt.load(buf=chunk_data) hm = chunk["Level"]["HeightMap"] hm.value[2] = 500 oldhm = numpy.array(hm.value) filename = mktemp("ChangedChunk") chunk.save(filename) changedChunk = nbt.load(filename) os.unlink(filename) eq = (changedChunk["Level"]["HeightMap"].value == oldhm) assert eq.all()
def testBigEndianIntHeightMap(tmpdir, temp_file): """ Test modifying, saving, and loading the new TAG_Int_Array heightmap added with the Anvil format. """ region = RegionFile(temp_file.strpath) chunk_data = region.readChunkBytes(0, 0) chunk = nbt.load(buf=chunk_data) hm = chunk["Level"]["HeightMap"] hm.value[2] = 500 oldhm = numpy.array(hm.value) filename = tmpdir.join("ChangedChunk").strpath chunk.save(filename) changedChunk = nbt.load(filename) os.unlink(filename) eq = (changedChunk["Level"]["HeightMap"].value == oldhm) assert eq.all()
def __init__(self, filename): self.filename = filename rootTag = nbt.load(filename) self.Blocks = array([[[pc_blocktypes.Chest.ID]]], 'uint8') for item in list(rootTag["Inventory"]): slot = item["Slot"].value if slot < 9 or slot >= 36: rootTag["Inventory"].remove(item) else: item["Slot"].value -= 9 # adjust for different chest slot indexes self.rootTag = rootTag
def testSpeed(self): d = join("test_files", "TileTicks_chunks.zip") zf = zipfile.ZipFile(d) files = [zf.read(f) for f in zf.namelist()[:40]] startTime = time.time() for f in files: if len(f): n = nbt.load(buf=f) duration = time.time() - startTime assert duration < 1.0 # Will fail when not using _nbt.pyx
def __init__(self, filename): self.filename = filename rootTag = nbt.load(filename) self.Blocks = array([[[pc_blocktypes.Chest.ID]]], 'uint8') for item in list(rootTag["Inventory"]): slot = item["Slot"].value if slot < 9 or slot >= 36: rootTag["Inventory"].remove(item) else: item[ "Slot"].value -= 9 # adjust for different chest slot indexes self.rootTag = rootTag
def getWorldInfo(cls, filename, displayNameLimit=40): try: if os.path.isdir(filename): folderName = os.path.basename(filename) levelDat = os.path.join(filename, "level.dat") else: folderName = os.path.basename(os.path.dirname(filename)) levelDat = filename levelTag = nbt.load(levelDat) try: displayName = levelTag['Data']['LevelName'].value if len(displayName) > displayNameLimit: displayName = displayName[:displayNameLimit] + "..." if len(folderName) > displayNameLimit: folderName = folderName[:displayNameLimit] + "..." if folderName != displayName: displayName = "%s (%s)" % (displayName, folderName) except Exception as e: log.warn("Failed to get display name for level.", exc_info=1) displayName = folderName try: lastPlayedTime = levelTag['Data']['LastPlayed'].value except Exception as e: log.warn("Failed to get last-played time for level.", exc_info=1) lastPlayedTime = 0 version = "Unknown Version" try: metadata = AnvilWorldMetadata(levelTag) stackVersion = VERSION_1_8 if metadata.is1_8World() else VERSION_1_7 if stackVersion == VERSION_1_7: version = "Minecraft 1.7" if "FML" in metadata.metadataTag: version = "MinecraftForge 1.7" if stackVersion == VERSION_1_8: version = "Minecraft 1.8" except Exception as e: log.warn("Failed to get version info for %s: %r", filename, e, exc_info=1) return WorldInfo(displayName, lastPlayedTime, version) except Exception as e: log.error("Failed getting world info for %s: %r", filename, e) return WorldInfo(str(e), 0, "")
def testLoad(self): "Load an indev level." level = nbt.load("test_files/indev.mclevel") # The root tag must have a name, and so must any tag within a TAG_Compound print level.name # Use the [] operator to look up subtags of a TAG_Compound. print level["Environment"]["SurroundingGroundHeight"].value # Numeric, string, and bytearray types have a value that can be accessed and changed. print level["Map"]["Blocks"].value return level
def testLoad(indev_file): "Load an indev level." level = nbt.load(indev_file.strpath) # The root tag must have a name, and so must any tag within a TAG_Compound print level.name # Use the [] operator to look up subtags of a TAG_Compound. print level["Environment"]["SurroundingGroundHeight"].value # Numeric, string, and bytearray types have a value that can be accessed and changed. print level["Map"]["Blocks"].value return level
def testErrors(created_nbt): """ attempt to name elements of a TAG_List named list elements are not allowed by the NBT spec, so we must discard any names when writing a list. """ level = created_nbt level["Map"]["Spawn"][0].name = "Torg Potter" data = level.save() newlevel = nbt.load(buf=data) n = newlevel["Map"]["Spawn"][0].name assert not n, "Named list element failed: %s" % n # attempt to delete non-existent TAG_Compound elements # this generates a KeyError like a python dict does. with pytest.raises(KeyError): del level["DEADBEEF"]
def readChunk(self, cx, cz, dimName): """ Return chunk (cx, cz) in the given dimension as an AnvilChunkData. Raise ChunkNotPresent if not found. :type cx: int or dtype :type cz: int or dtype :type dimName: str :return: :rtype: AnvilChunkData """ try: data = self.selectedRevision.readChunkBytes(cx, cz, dimName) chunkTag = nbt.load(buf=data) log.debug("_getChunkData: Chunk %s loaded (%s bytes)", (cx, cz), len(data)) chunkData = AnvilChunkData(self, cx, cz, dimName, chunkTag) except ChunkNotPresent: raise except (KeyError, IndexError, zlib.error) as e: # Missing nbt keys, lists too short, decompression failure raise AnvilChunkFormatError("Error loading chunk: %r" % e) return chunkData
def getPlayerTag(self, playerUUID=""): """ Return the root NBT tag for the named player. Raise PlayerNotFound if not present. :param playerUUID: :type playerUUID: unicode :return: :rtype: PCPlayer """ if playerUUID == "": if "Player" in self.metadata.rootTag: # single-player world playerTag = self.metadata.rootTag["Player"] return playerTag raise PlayerNotFound(playerUUID) else: playerFilePath = "playerdata/%s.dat" % playerUUID if self.selectedRevision.containsFile(playerFilePath): # multiplayer world, found this player playerTag = nbt.load(buf=self.selectedRevision.readFile(playerFilePath)) return playerTag else: raise PlayerNotFound(playerUUID)
def __init__(self, shape=None, filename=None, blocktypes='Alpha', readonly=False, resume=False): """ Creates an object which stores a section of a Minecraft world as an NBT structure. The order of the coordinates for the block arrays in the file is y,z,x. This is the same order used in Minecraft 1.4's chunk sections. Parameters ---------- shape: tuple of int The shape of the schematic as (x, y, z) filename: basestring Path to a file to load a saved schematic from. blocktypes: basestring or BlockTypeSet The name of a builtin blocktypes set (one of "Classic", "Alpha", "Pocket") to indicate allowable blocks. The default is Alpha. An instance of BlockTypeSet may be passed instead. Returns ---------- SchematicFileAdapter """ self.EntityRef = PCEntityRef self.TileEntityRef = PCTileEntityRef if filename is None and shape is None: raise ValueError("shape or filename required to create %s" % self.__class__.__name__) if filename: self.filename = filename if os.path.exists(filename): rootTag = nbt.load(filename) else: rootTag = None else: self.filename = None rootTag = None if blocktypes in blocktypeClassesByName: self.blocktypes = blocktypeClassesByName[blocktypes]() else: if not isinstance(blocktypes, BlockTypeSet): raise ValueError("%s is not a recognized BlockTypeSet", blocktypes) self.blocktypes = blocktypes if rootTag: self.rootTag = rootTag if "Materials" in rootTag: self.blocktypes = blocktypeClassesByName[self.Materials]() else: rootTag["Materials"] = nbt.TAG_String(self.blocktypes.name) w = self.rootTag["Width"].value l = self.rootTag["Length"].value h = self.rootTag["Height"].value assert self.rootTag["Blocks"].value.size == w * l * h self._Blocks = self.rootTag["Blocks"].value.astype('uint16').reshape(h, l, w) # _Blocks is y, z, x del self.rootTag["Blocks"] if "AddBlocks" in self.rootTag: # Use WorldEdit's "AddBlocks" array to load and store the 4 high bits of a block ID. # Unlike Minecraft's NibbleArrays, this array stores the first block's bits in the # 4 high bits of the first byte. size = (h * l * w) # If odd, add one to the size to make sure the adjacent slices line up. add = numpy.empty(size + (size & 1), 'uint16') # Fill the even bytes with data add[::2] = self.rootTag["AddBlocks"].value # Copy the low 4 bits to the odd bytes add[1::2] = add[::2] & 0xf # Shift the even bytes down add[::2] >>= 4 # Shift every byte up before merging it with Blocks add <<= 8 self._Blocks |= add[:size].reshape(h, l, w) del self.rootTag["AddBlocks"] self.rootTag["Data"].value = self.rootTag["Data"].value.reshape(h, l, w) if "Biomes" in self.rootTag: self.rootTag["Biomes"].value.shape = (l, w) # If BlockIDs is present, it contains an ID->internalName mapping # from the source level's FML tag. if "BlockIDs" in self.rootTag: self.blocktypes.addBlockIDsFromSchematicTag(self.rootTag["BlockIDs"]) # If itemStackVersion is present, it was exported from MCEdit 2.0. # Its value is either 17 or 18, the values of the version constants. # ItemIDs will also be present. # If itemStackVersion is not present, this schematic was exported from # WorldEdit or MCEdit 1.0. The itemStackVersion cannot be determined # without searching the entities for an itemStack and checking # the type of its `id` tag. If no itemStacks are found, the # version defaults to 1.8 which does not need an ItemIDs tag. if "itemStackVersion" in self.rootTag: itemStackVersion = self.rootTag["itemStackVersion"].value if itemStackVersion not in (VERSION_1_7, VERSION_1_8): raise LevelFormatError("Unknown item stack version %d" % itemStackVersion) if itemStackVersion == VERSION_1_7: itemIDs = self.rootTag.get("ItemIDs") if itemIDs is not None: self.blocktypes.addItemIDsFromSchematicTag(itemIDs) self.blocktypes.itemStackVersion = itemStackVersion else: self.blocktypes.itemStackVersion = self.getItemStackVersionFromEntities() else: rootTag = nbt.TAG_Compound(name="Schematic") rootTag["Height"] = nbt.TAG_Short(shape[1]) rootTag["Length"] = nbt.TAG_Short(shape[2]) rootTag["Width"] = nbt.TAG_Short(shape[0]) rootTag["Entities"] = nbt.TAG_List() rootTag["TileEntities"] = nbt.TAG_List() rootTag["Materials"] = nbt.TAG_String(self.blocktypes.name) rootTag["itemStackVersion"] = nbt.TAG_Byte(self.blocktypes.itemStackVersion) self._Blocks = zeros((shape[1], shape[2], shape[0]), 'uint16') rootTag["Data"] = nbt.TAG_Byte_Array(zeros((shape[1], shape[2], shape[0]), uint8)) rootTag["Biomes"] = nbt.TAG_Byte_Array(zeros((shape[2], shape[0]), uint8)) self.rootTag = rootTag self.rootTag["BlockIDs"] = blockIDMapping(self.blocktypes) itemMapping = itemIDMapping(self.blocktypes) if itemMapping is not None: self.rootTag["ItemIDs"] = itemMapping # Only present for Forge 1.7 # Expand blocks and data to chunk edges h16 = (self.Height + 15) & ~0xf l16 = (self.Length + 15) & ~0xf w16 = (self.Width + 15) & ~0xf blocks = self._Blocks self._Blocks = numpy.zeros((h16, l16, w16), blocks.dtype) self._Blocks[:blocks.shape[0], :blocks.shape[1], :blocks.shape[2]] = blocks data = self.rootTag["Data"].value self.rootTag["Data"].value = numpy.zeros((h16, l16, w16), data.dtype) self.rootTag["Data"].value[:data.shape[0], :data.shape[1], :data.shape[2]] = data self.rootTag["Data"].value &= 0xF # discard high bits self.entitiesByChunk = defaultdict(list) for tag in self.rootTag["Entities"]: ref = self.EntityRef(tag) pos = ref.Position cx, cy, cz = pos.chunkPos() self.entitiesByChunk[cx, cz].append(tag) self.tileEntitiesByChunk = defaultdict(list) for tag in self.rootTag["TileEntities"]: ref = self.TileEntityRef(tag) pos = ref.Position cx, cy, cz = pos.chunkPos() self.tileEntitiesByChunk[cx, cz].append(tag)
def repair(self): """ Fix the following problems with the region file: - remove offset table entries pointing past the end of the file - remove entries that overlap other entries - relocate offsets for chunks whose xPos,yPos don't match """ lostAndFound = {} _freeSectors = [True] * len(self.freeSectors) _freeSectors[0] = _freeSectors[1] = False deleted = 0 recovered = 0 log.info("Beginning repairs on {file} ({chunks} chunks)".format( file=os.path.basename(self.path), chunks=sum(self.offsets > 0))) for index, offset in enumerate(self.offsets): if offset: cx = index & 0x1f cz = index >> 5 sectorStart = offset >> 8 sectorCount = offset & 0xff try: if sectorStart + sectorCount > len(self.freeSectors): raise RegionFormatError( "Offset {start}:{end} ({offset}) at index {index} pointed outside of " "the file".format(start=sectorStart, end=sectorStart + sectorCount, index=index, offset=offset)) data = self.readChunkBytes(cx, cz) chunkTag = nbt.load(buf=data) lev = chunkTag["Level"] xPos = lev["xPos"].value & 0x1f zPos = lev["zPos"].value & 0x1f overlaps = False for i in xrange(sectorStart, sectorStart + sectorCount): if _freeSectors[i] is False: overlaps = True _freeSectors[i] = False if xPos != cx or zPos != cz: lostAndFound[xPos, zPos] = data raise RegionFormatError( "Chunk {found} was found in the slot reserved for {expected}" .format(found=(xPos, zPos), expected=(cx, cz))) if overlaps: raise RegionFormatError( "Chunk {found} (in slot {expected}) has overlapping sectors with another chunk!" .format(found=(xPos, zPos), expected=(cx, cz))) except Exception as e: log.info( "Unexpected chunk data at sector {sector} ({exc})". format(sector=sectorStart, exc=e)) self._setOffset(cx, cz, 0) deleted += 1 for cPos, foundData in lostAndFound.iteritems(): cx, cz = cPos if self._getOffset(cx, cz) == 0: log.info( "Found chunk {found} and its slot is empty, recovering it". format(found=cPos)) self.writeChunkBytes(cx, cz, foundData) recovered += 1 log.info( "Repair complete. Removed {0} chunks, recovered {1} chunks, net {2}" .format(deleted, recovered, recovered - deleted))
def __init__(self, filename=None, create=False, readonly=False, resume=None): """ Load a Minecraft for PC level (Anvil format) from the given filename. It can point to either a level.dat or a folder containing one. If create is True, it will also create the world using a randomly selected seed. If you try to create an existing world, IOError will be raised. Uses a RevisionHistory to manage undo history. Upon creation, the world is read-only until createRevision() is called. Call createRevision() to create a new revision, or selectRevision() to revert to an earlier revision. Older revisions are read-only, so createRevision() must be called again to make further changes. Call writeAllChanges() to write all changes into the original world. :type filename: str or unicode :type create: bool :type readonly: bool :rtype: AnvilWorldAdapter """ self.lockTime = 0 assert not (create and readonly) if os.path.basename(filename) in ("level.dat", "level.dat_old"): filename = os.path.dirname(filename) if not os.path.exists(filename): if not create: raise IOError('File not found') os.mkdir(filename) else: if create: if not os.path.isdir(filename) or os.path.exists(os.path.join(filename, "level.dat")): raise IOError('File exists!') if not os.path.isdir(filename): raise IOError('File is not a Minecraft Anvil world') if readonly: self.revisionHistory = AnvilWorldFolder(filename) self.selectedRevision = self.revisionHistory else: self.revisionHistory = RevisionHistory(filename, resume) self.selectedRevision = self.revisionHistory.getHead() self.filename = filename self.readonly = readonly if not readonly: self.acquireSessionLock() if create: self._createMetadataTag() self.selectedRevision.writeFile("level.dat", self.metadata.metadataTag.save()) else: try: metadataTag = nbt.load(buf=self.selectedRevision.readFile("level.dat")) self.metadata = AnvilWorldMetadata(metadataTag) self.loadFMLMapping() except (EnvironmentError, zlib.error) as e: log.info("Error loading level.dat, trying level.dat_old ({0})".format(e)) try: metadataTag = nbt.load(buf=self.selectedRevision.readFile("level.dat_old")) self.metadata = AnvilWorldMetadata(metadataTag) log.info("level.dat restored from backup.") self.saveChanges() except Exception as e: traceback.print_exc() log.info("%r while loading level.dat_old. Initializing with defaults.", e) self._createMetadataTag() assert self.metadata.version == VERSION_ANVIL, "Pre-Anvil world formats are not supported (for now)"
def __init__(self, shape=None, filename=None, blocktypes='Alpha', readonly=False, resume=False): """ Creates an object which stores a section of a Minecraft world as an NBT structure. The order of the coordinates for the block arrays in the file is y,z,x. This is the same order used in Minecraft 1.4's chunk sections. :type shape: tuple :param shape: The shape of the schematic as (x, y, z) :type filename: basestring :param filename: Path to a file to load a saved schematic from. :type blocktypes: basestring or BlockTypeSet :param blocktypes: The name of a builtin blocktypes set (one of "Classic", "Alpha", "Pocket") to indicate allowable blocks. The default is Alpha. An instance of BlockTypeSet may be passed instead. :rtype: SchematicFileAdapter """ if filename is None and shape is None: raise ValueError("shape or filename required to create %s" % self.__class__.__name__) if filename: self.filename = filename if os.path.exists(filename): rootTag = nbt.load(filename) else: rootTag = None else: self.filename = None rootTag = None if blocktypes in blocktypes_named: self.blocktypes = blocktypes_named[blocktypes] else: assert(isinstance(blocktypes, BlockTypeSet)) self.blocktypes = blocktypes if rootTag: self.rootTag = rootTag if "Materials" in rootTag: self.blocktypes = blocktypes_named[self.Materials] else: rootTag["Materials"] = nbt.TAG_String(self.blocktypes.name) w = self.rootTag["Width"].value l = self.rootTag["Length"].value h = self.rootTag["Height"].value assert self.rootTag["Blocks"].value.size == w * l * h self._Blocks = self.rootTag["Blocks"].value.astype('uint16').reshape(h, l, w) # _Blocks is y, z, x del self.rootTag["Blocks"] if "AddBlocks" in self.rootTag: # Use WorldEdit's "AddBlocks" array to load and store the 4 high bits of a block ID. # Unlike Minecraft's NibbleArrays, this array stores the first block's bits in the # 4 high bits of the first byte. size = (h * l * w) # If odd, add one to the size to make sure the adjacent slices line up. add = numpy.empty(size + (size & 1), 'uint16') # Fill the even bytes with data add[::2] = self.rootTag["AddBlocks"].value # Copy the low 4 bits to the odd bytes add[1::2] = add[::2] & 0xf # Shift the even bytes down add[::2] >>= 4 # Shift every byte up before merging it with Blocks add <<= 8 self._Blocks |= add[:size].reshape(h, l, w) del self.rootTag["AddBlocks"] self.rootTag["Data"].value = self.rootTag["Data"].value.reshape(h, l, w) if "Biomes" in self.rootTag: self.rootTag["Biomes"].value.shape = (l, w) if "BlockIDs" in self.rootTag or "ItemIDs" in self.rootTag: self.blocktypes = type(self.blocktypes)() if "BlockIDs" in self.rootTag: self.blocktypes.addBlockIDsFromSchematicTag(self.rootTag["BlockIDs"]) if "ItemIDs" in self.rootTag: self.blocktypes.addItemIDsFromSchematicTag(self.rootTag["ItemIDs"]) else: rootTag = nbt.TAG_Compound(name="Schematic") rootTag["Height"] = nbt.TAG_Short(shape[1]) rootTag["Length"] = nbt.TAG_Short(shape[2]) rootTag["Width"] = nbt.TAG_Short(shape[0]) rootTag["Entities"] = nbt.TAG_List() rootTag["TileEntities"] = nbt.TAG_List() rootTag["Materials"] = nbt.TAG_String(self.blocktypes.name) self._Blocks = zeros((shape[1], shape[2], shape[0]), 'uint16') rootTag["Data"] = nbt.TAG_Byte_Array(zeros((shape[1], shape[2], shape[0]), uint8)) rootTag["Biomes"] = nbt.TAG_Byte_Array(zeros((shape[2], shape[0]), uint8)) self.rootTag = rootTag self.rootTag["BlockIDs"] = blockIDMapping(blocktypes) self.rootTag["ItemIDs"] = itemIDMapping(blocktypes) #expand blocks and data to chunk edges h16 = (self.Height + 15) & ~0xf l16 = (self.Length + 15) & ~0xf w16 = (self.Width + 15) & ~0xf blocks = self._Blocks self._Blocks = numpy.zeros((h16, l16, w16), blocks.dtype) self._Blocks[:blocks.shape[0], :blocks.shape[1], :blocks.shape[2]] = blocks data = self.rootTag["Data"].value self.rootTag["Data"].value = numpy.zeros((h16, l16, w16), data.dtype) self.rootTag["Data"].value[:data.shape[0], :data.shape[1], :data.shape[2]] = data self.rootTag["Data"].value &= 0xF # discard high bits self.entitiesByChunk = defaultdict(list) for tag in self.rootTag["Entities"]: ref = self.EntityRef(tag) pos = ref.Position cx, cy, cz = pos.chunkPos() self.entitiesByChunk[cx, cz].append(tag) self.tileEntitiesByChunk = defaultdict(list) for tag in self.rootTag["TileEntities"]: ref = self.TileEntityRef(tag) pos = ref.Position cx, cy, cz = pos.chunkPos() self.tileEntitiesByChunk[cx, cz].append(tag)
def readChunk(rev, cx, cz): return nbt.load(buf=rev.readChunkBytes(cx, cz, ""))
def load_file(): global test_file test_file = nbt.load(buf=test_data)
def __init__(self, shape=None, filename=None, blocktypes='Alpha', readonly=False, resume=False): """ Creates an object which stores a section of a Minecraft world as an NBT structure. The order of the coordinates for the block arrays in the file is y,z,x. This is the same order used in Minecraft 1.4's chunk sections. :type shape: tuple :param shape: The shape of the schematic as (x, y, z) :type filename: basestring :param filename: Path to a file to load a saved schematic from. :type blocktypes: basestring or BlockTypeSet :param blocktypes: The name of a builtin blocktypes set (one of "Classic", "Alpha", "Pocket") to indicate allowable blocks. The default is Alpha. An instance of BlockTypeSet may be passed instead. :rtype: SchematicFileAdapter """ if filename is None and shape is None: raise ValueError("shape or filename required to create %s" % self.__class__.__name__) if filename: self.filename = filename if os.path.exists(filename): rootTag = nbt.load(filename) else: rootTag = None else: self.filename = None rootTag = None if blocktypes in blocktypes_named: self.blocktypes = blocktypes_named[blocktypes] else: assert (isinstance(blocktypes, BlockTypeSet)) self.blocktypes = blocktypes if rootTag: self.rootTag = rootTag if "Materials" in rootTag: self.blocktypes = blocktypes_named[self.Materials] else: rootTag["Materials"] = nbt.TAG_String(self.blocktypes.name) w = self.rootTag["Width"].value l = self.rootTag["Length"].value h = self.rootTag["Height"].value assert self.rootTag["Blocks"].value.size == w * l * h self._Blocks = self.rootTag["Blocks"].value.astype( 'uint16').reshape(h, l, w) # _Blocks is y, z, x del self.rootTag["Blocks"] if "AddBlocks" in self.rootTag: # Use WorldEdit's "AddBlocks" array to load and store the 4 high bits of a block ID. # Unlike Minecraft's NibbleArrays, this array stores the first block's bits in the # 4 high bits of the first byte. size = (h * l * w) # If odd, add one to the size to make sure the adjacent slices line up. add = numpy.empty(size + (size & 1), 'uint16') # Fill the even bytes with data add[::2] = self.rootTag["AddBlocks"].value # Copy the low 4 bits to the odd bytes add[1::2] = add[::2] & 0xf # Shift the even bytes down add[::2] >>= 4 # Shift every byte up before merging it with Blocks add <<= 8 self._Blocks |= add[:size].reshape(h, l, w) del self.rootTag["AddBlocks"] self.rootTag["Data"].value = self.rootTag["Data"].value.reshape( h, l, w) if "Biomes" in self.rootTag: self.rootTag["Biomes"].value.shape = (l, w) else: rootTag = nbt.TAG_Compound(name="Schematic") rootTag["Height"] = nbt.TAG_Short(shape[1]) rootTag["Length"] = nbt.TAG_Short(shape[2]) rootTag["Width"] = nbt.TAG_Short(shape[0]) rootTag["Entities"] = nbt.TAG_List() rootTag["TileEntities"] = nbt.TAG_List() rootTag["Materials"] = nbt.TAG_String(self.blocktypes.name) self._Blocks = zeros((shape[1], shape[2], shape[0]), 'uint16') rootTag["Data"] = nbt.TAG_Byte_Array( zeros((shape[1], shape[2], shape[0]), uint8)) rootTag["Biomes"] = nbt.TAG_Byte_Array( zeros((shape[2], shape[0]), uint8)) self.rootTag = rootTag #expand blocks and data to chunk edges h16 = (self.Height + 15) & ~0xf l16 = (self.Length + 15) & ~0xf w16 = (self.Width + 15) & ~0xf blocks = self._Blocks self._Blocks = numpy.zeros((h16, l16, w16), blocks.dtype) self._Blocks[:blocks.shape[0], :blocks.shape[1], :blocks. shape[2]] = blocks data = self.rootTag["Data"].value self.rootTag["Data"].value = numpy.zeros((h16, l16, w16), data.dtype) self.rootTag["Data"].value[:data.shape[0], :data.shape[1], :data. shape[2]] = data self.rootTag["Data"].value &= 0xF # discard high bits self.Entities = [ self.EntityRef(tag) for tag in self.rootTag["Entities"] ] self.TileEntities = [ self.EntityRef(tag) for tag in self.rootTag["TileEntities"] ]
def testRevision(history): revA = history.createRevision() log.info("revA") # rev A @1 - touch chunk 1 cx, cz = iter(revA.chunkPositions("")).next() chunk = readChunkTag(revA, cx, cz) old_tag = tag = nbt.load(buf=history.rootFolder.readChunkBytes(cx, cz, "")) assert readChunkTag(history.rootNode, cx, cz) == tag assert chunk == tag chunk["Level"]["test"] = nbt.TAG_String("test string") writeChunkTag(revA, cx, cz, chunk) tag = readChunkTag(revA, cx, cz) assert "test" in tag["Level"] and tag["Level"]["test"].value == "test string" revB = history.createRevision() log.info("revB") # rev B @2 - delete chunk 2 tag = readChunkTag(revB, cx, cz) assert "test" in tag["Level"] and tag["Level"]["test"].value == "test string" revB.deleteChunk(cx+1, cz, "") assert not revB.containsChunk(cx+1, cz, "") revC = history.createRevision() log.info("revC") # rev C @3 - delete file assert not revC.containsChunk(cx+1, cz, "") revC.deleteFile("level.dat") assert not revC.containsFile("level.dat") changes = revC.getChanges() assert changes.chunks[""] == set() assert changes.files == {"level.dat"} tailRev = history.getRevision(0) history.writeAllChanges() # initial folder (rev idx 0) and following nodes replaced by reverse nodes # rev C @3 replaced by initial folder assert revC.invalid revC = history.getHead() assert tailRev is revC changes = revC.getChanges() assert changes.chunks[""] == set() assert changes.files == {"level.dat"} # rev D - create chunk 3 revD = history.createRevision() log.info("revD") assert not revD.containsFile("level.dat") writeChunkTag(revD, 1000, 1000, old_tag) assert not history.rootFolder.containsFile("level.dat") assert not history.rootFolder.containsChunk(cx+1, cz, "") assert "test" in tag["Level"] tag = readChunkTag(history.rootFolder, cx, cz) assert tag != old_tag assert "test" in tag["Level"] # grab rev B revBagain = history.getRevision(2) assert "test" in readChunkTag(revBagain, cx, cz)["Level"] assert not revBagain.containsChunk(cx+1, cz, "") # rev B should be read only with pytest.raises(IOError): writeChunkTag(revBagain, cx, cz, old_tag) # check all changes so far allChanges = history.getRevisionChanges(0, revD) assert allChanges.chunks[""] == {(cx, cz), (cx+1, cz), (1000, 1000)} assert allChanges.files == {"level.dat"} # insert rev E after rev B # world folder is now at the end of an orphan chain at rev @2 # orphaned revisions are read only and still valid, but do not appear in the history revE = history.createRevision(2) assert "test" in readChunkTag(revE, cx, cz)["Level"] assert not revE.containsChunk(cx+1, cz, "") history.close()
def testLoadUncompressed(temp_file): rootTag = nbt.load(temp_file.strpath)
def __init__(self, shape=None, filename=None, blocktypes='Alpha', readonly=False, resume=False): """ Creates an object which stores a section of a Minecraft world as an NBT structure. The order of the coordinates for the block arrays in the file is y,z,x. This is the same order used in Minecraft 1.4's chunk sections. :type shape: tuple :param shape: The shape of the schematic as (x, y, z) :type filename: basestring :param filename: Path to a file to load a saved schematic from. :type blocktypes: basestring or BlockTypeSet :param blocktypes: The name of a builtin blocktypes set (one of "Classic", "Alpha", "Pocket") to indicate allowable blocks. The default is Alpha. An instance of BlockTypeSet may be passed instead. :rtype: SchematicFileAdapter """ self.EntityRef = PCEntityRef self.TileEntityRef = PCTileEntityRef if filename is None and shape is None: raise ValueError("shape or filename required to create %s" % self.__class__.__name__) if filename: self.filename = filename if os.path.exists(filename): rootTag = nbt.load(filename) else: rootTag = None else: self.filename = None rootTag = None if blocktypes in blocktypeClassesByName: self.blocktypes = blocktypeClassesByName[blocktypes]() else: assert (isinstance(blocktypes, BlockTypeSet)) self.blocktypes = blocktypes if rootTag: self.rootTag = rootTag if "Materials" in rootTag: self.blocktypes = blocktypeClassesByName[self.Materials]() else: rootTag["Materials"] = nbt.TAG_String(self.blocktypes.name) w = self.rootTag["Width"].value l = self.rootTag["Length"].value h = self.rootTag["Height"].value assert self.rootTag["Blocks"].value.size == w * l * h self._Blocks = self.rootTag["Blocks"].value.astype( 'uint16').reshape(h, l, w) # _Blocks is y, z, x del self.rootTag["Blocks"] if "AddBlocks" in self.rootTag: # Use WorldEdit's "AddBlocks" array to load and store the 4 high bits of a block ID. # Unlike Minecraft's NibbleArrays, this array stores the first block's bits in the # 4 high bits of the first byte. size = (h * l * w) # If odd, add one to the size to make sure the adjacent slices line up. add = numpy.empty(size + (size & 1), 'uint16') # Fill the even bytes with data add[::2] = self.rootTag["AddBlocks"].value # Copy the low 4 bits to the odd bytes add[1::2] = add[::2] & 0xf # Shift the even bytes down add[::2] >>= 4 # Shift every byte up before merging it with Blocks add <<= 8 self._Blocks |= add[:size].reshape(h, l, w) del self.rootTag["AddBlocks"] self.rootTag["Data"].value = self.rootTag["Data"].value.reshape( h, l, w) if "Biomes" in self.rootTag: self.rootTag["Biomes"].value.shape = (l, w) # If BlockIDs is present, it contains an ID->internalName mapping # from the source level's FML tag. if "BlockIDs" in self.rootTag: self.blocktypes.addBlockIDsFromSchematicTag( self.rootTag["BlockIDs"]) # If itemStackVersion is present, it was exported from MCEdit 2.0. # Its value is either 17 or 18, the values of the version constants. # ItemIDs will also be present. # If itemStackVersion is not present, this schematic was exported from # WorldEdit or MCEdit 1.0. The itemStackVersion cannot be determined # without searching the entities for an itemStack and checking # the type of its `id` tag. If no itemStacks are found, the # version defaults to 1.8 which does not need an ItemIDs tag. if "itemStackVersion" in self.rootTag: itemStackVersion = self.rootTag["itemStackVersion"].value if itemStackVersion not in (VERSION_1_7, VERSION_1_8): raise LevelFormatError("Unknown item stack version %d" % itemStackVersion) if itemStackVersion == VERSION_1_7: itemIDs = self.rootTag.get("ItemIDs") if itemIDs is not None: self.blocktypes.addItemIDsFromSchematicTag(itemIDs) self.blocktypes.itemStackVersion = itemStackVersion else: self.blocktypes.itemStackVersion = self.getItemStackVersionFromEntities( ) else: rootTag = nbt.TAG_Compound(name="Schematic") rootTag["Height"] = nbt.TAG_Short(shape[1]) rootTag["Length"] = nbt.TAG_Short(shape[2]) rootTag["Width"] = nbt.TAG_Short(shape[0]) rootTag["Entities"] = nbt.TAG_List() rootTag["TileEntities"] = nbt.TAG_List() rootTag["Materials"] = nbt.TAG_String(self.blocktypes.name) rootTag["itemStackVersion"] = nbt.TAG_Byte( self.blocktypes.itemStackVersion) self._Blocks = zeros((shape[1], shape[2], shape[0]), 'uint16') rootTag["Data"] = nbt.TAG_Byte_Array( zeros((shape[1], shape[2], shape[0]), uint8)) rootTag["Biomes"] = nbt.TAG_Byte_Array( zeros((shape[2], shape[0]), uint8)) self.rootTag = rootTag self.rootTag["BlockIDs"] = blockIDMapping(blocktypes) itemMapping = itemIDMapping(blocktypes) if itemMapping is not None: self.rootTag[ "ItemIDs"] = itemMapping # Only present for Forge 1.7 # Expand blocks and data to chunk edges h16 = (self.Height + 15) & ~0xf l16 = (self.Length + 15) & ~0xf w16 = (self.Width + 15) & ~0xf blocks = self._Blocks self._Blocks = numpy.zeros((h16, l16, w16), blocks.dtype) self._Blocks[:blocks.shape[0], :blocks.shape[1], :blocks. shape[2]] = blocks data = self.rootTag["Data"].value self.rootTag["Data"].value = numpy.zeros((h16, l16, w16), data.dtype) self.rootTag["Data"].value[:data.shape[0], :data.shape[1], :data. shape[2]] = data self.rootTag["Data"].value &= 0xF # discard high bits self.entitiesByChunk = defaultdict(list) for tag in self.rootTag["Entities"]: ref = self.EntityRef(tag) pos = ref.Position cx, cy, cz = pos.chunkPos() self.entitiesByChunk[cx, cz].append(tag) self.tileEntitiesByChunk = defaultdict(list) for tag in self.rootTag["TileEntities"]: ref = self.TileEntityRef(tag) pos = ref.Position cx, cy, cz = pos.chunkPos() self.tileEntitiesByChunk[cx, cz].append(tag)
def testLoadUncompressed(self): rootTag = nbt.load("test_files/uncompressed.nbt")
def testLoadUncompressed(self): rootTag = nbt.load(TempFile("uncompressed.nbt"))