diff --git a/overviewer_core/nbt.py b/overviewer_core/nbt.py
index cd2ba59..fa494ab 100644
--- a/overviewer_core/nbt.py
+++ b/overviewer_core/nbt.py
@@ -13,10 +13,12 @@
# You should have received a copy of the GNU General Public License along
# with the Overviewer. If not, see .
-import gzip, zlib
-import struct
-import StringIO
import functools
+import gzip
+import StringIO
+import struct
+import zlib
+
# decorator that turns the first argument from a string into an open file
# handle
@@ -29,13 +31,15 @@ def _file_loader(func):
return func(fileobj, *args)
return wrapper
+
@_file_loader
def load(fileobj):
"""Reads in the given file as NBT format, parses it, and returns the
- result as a (name, data) tuple.
+ result as a (name, data) tuple.
"""
return NBTFileReader(fileobj).read_all()
+
@_file_loader
def load_region(fileobj):
"""Reads in the given file as a MCR region, and returns an object
@@ -45,23 +49,29 @@ def load_region(fileobj):
class CorruptionError(Exception):
pass
+
+
class CorruptRegionError(CorruptionError):
"""An exception raised when the MCRFileReader class encounters an
error during region file parsing.
"""
pass
+
+
class CorruptChunkError(CorruptionError):
pass
+
+
class CorruptNBTError(CorruptionError):
"""An exception raised when the NBTFileReader class encounters
something unexpected in an NBT file."""
pass
+
class NBTFileReader(object):
"""Low level class that reads the Named Binary Tag format used by Minecraft
"""
-
# compile the unpacker's into a classes
_byte = struct.Struct("b")
_short = struct.Struct(">h")
@@ -70,8 +80,8 @@ class NBTFileReader(object):
_uint = struct.Struct(">I")
_long = struct.Struct(">q")
_float = struct.Struct(">f")
- _double = struct.Struct(">d")
-
+ _double = struct.Struct(">d")
+
def __init__(self, fileobj, is_gzip=True):
"""Create a NBT parsing object with the given file-like
object. Setting is_gzip to False parses the file as a zlib
@@ -96,9 +106,9 @@ class NBTFileReader(object):
7: self._read_tag_byte_array,
8: self._read_tag_string,
9: self._read_tag_list,
- 10:self._read_tag_compound,
- 11:self._read_tag_int_array,
- 12:self._read_tag_long_array,
+ 10: self._read_tag_compound,
+ 11: self._read_tag_int_array,
+ 12: self._read_tag_long_array,
}
# These private methods read the payload only of the following types
@@ -109,7 +119,7 @@ class NBTFileReader(object):
def _read_tag_byte(self):
byte = self._file.read(1)
return self._byte.unpack(byte)[0]
-
+
def _read_tag_short(self):
bytes = self._file.read(2)
return self._short.unpack(bytes)[0]
@@ -137,12 +147,12 @@ class NBTFileReader(object):
def _read_tag_int_array(self):
length = self._uint.unpack(self._file.read(4))[0]
- int_bytes = self._file.read(length*4)
+ int_bytes = self._file.read(length * 4)
return struct.unpack(">%ii" % length, int_bytes)
def _read_tag_long_array(self):
length = self._uint.unpack(self._file.read(4))[0]
- long_bytes = self._file.read(length*8)
+ long_bytes = self._file.read(length * 8)
return struct.unpack(">%iq" % length, long_bytes)
def _read_tag_string(self):
@@ -177,7 +187,7 @@ class NBTFileReader(object):
tags[name] = payload
return tags
-
+
def read_all(self):
"""Reads the entire file and returns (name, payload)
name is the name of the root tag, and payload is a dictionary mapping
@@ -189,15 +199,14 @@ class NBTFileReader(object):
tagtype = ord(self._file.read(1))
if tagtype != 10:
raise Exception("Expected a tag compound")
-
# Read the tag name
name = self._read_tag_string()
payload = self._read_tag_compound()
-
return (name, payload)
except (struct.error, ValueError, TypeError), e:
raise CorruptNBTError("could not parse nbt: %s" % (str(e),))
+
# For reference, the MCR format is outlined at
#
class MCRFileReader(object):
@@ -206,16 +215,16 @@ class MCRFileReader(object):
chunks (as (name, data) tuples), getting chunk timestamps, and for
listing chunks contained in the file.
"""
-
+
_location_table_format = struct.Struct(">1024I")
_timestamp_table_format = struct.Struct(">1024i")
_chunk_header_format = struct.Struct(">I B")
-
+
def __init__(self, fileobj):
"""This creates a region object from the given file-like
object. Chances are you want to use load_region instead."""
self._file = fileobj
-
+
# read in the location table
location_data = self._file.read(4096)
if not len(location_data) == 4096:
@@ -234,29 +243,29 @@ class MCRFileReader(object):
with keeping it open. Using this object after closing it
results in undefined behaviour.
"""
-
+
self._file.close()
self._file = None
- def get_chunks(self):
+ def get_chunks(self):
"""Return an iterator of all chunks contained in this region
file, as (x, z) coordinate tuples. To load these chunks,
provide these coordinates to load_chunk()."""
-
- for x in xrange(32):
- for z in xrange(32):
+
+ for x in xrange(32):
+ for z in xrange(32):
if self._locations[x + z * 32] >> 8 != 0:
- yield (x,z)
-
+ yield (x, z)
+
def get_chunk_timestamp(self, x, z):
"""Return the given chunk's modification time. If the given
chunk doesn't exist, this number may be nonsense. Like
load_chunk(), this will wrap x and z into the range [0, 31].
"""
x = x % 32
- z = z % 32
- return self._timestamps[x + z * 32]
-
+ z = z % 32
+ return self._timestamps[x + z * 32]
+
def chunk_exists(self, x, z):
"""Determines if a chunk exists."""
x = x % 32
@@ -273,40 +282,42 @@ class MCRFileReader(object):
x = x % 32
z = z % 32
location = self._locations[x + z * 32]
- offset = (location >> 8) * 4096;
- sectors = location & 0xff;
-
+ offset = (location >> 8) * 4096
+ sectors = location & 0xff
+
if offset == 0:
return None
-
+
# seek to the data
self._file.seek(offset)
-
+
# read in the chunk data header
header = self._file.read(5)
if len(header) != 5:
raise CorruptChunkError("chunk header is invalid")
- data_length, compression = self._chunk_header_format.unpack(header)
-
+ data_length, compression = self._chunk_header_format.unpack(header)
+
# figure out the compression
is_gzip = True
if compression == 1:
- # gzip -- not used by the official client, but trivial to support here so...
+ # gzip -- not used by the official client, but trivial to
+ # support here so...
is_gzip = True
elif compression == 2:
# deflate -- pure zlib stream
is_gzip = False
else:
# unsupported!
- raise CorruptRegionError("unsupported chunk compression type: %i (should be 1 or 2)" % (compression,))
-
+ raise CorruptRegionError("unsupported chunk compression type: %i "
+ "(should be 1 or 2)" % (compression,))
+
# turn the rest of the data into a StringIO object
# (using data_length - 1, as we already read 1 byte for compression)
data = self._file.read(data_length - 1)
if len(data) != data_length - 1:
raise CorruptRegionError("chunk length is invalid")
data = StringIO.StringIO(data)
-
+
try:
return NBTFileReader(data, is_gzip=is_gzip).read_all()
except CorruptionError: