239 lines
7.3 KiB
Python
239 lines
7.3 KiB
Python
# https://github.com/minetest/minetest/blob/master/doc/world_format.txt#L301
|
|
# https://docs.python.org/3/library/struct.html
|
|
import collections
|
|
|
|
import pyzstd
|
|
|
|
from stream import StreamReader
|
|
|
|
MAP_BLOCKSIZE = 16
|
|
|
|
|
|
vector = collections.namedtuple('vector', ('x', 'y', 'z'))
|
|
|
|
|
|
def unpack_pos(packed_pos):
|
|
# 16*(16*z + y) + x
|
|
zy, x = divmod(packed_pos, 16)
|
|
z, y = divmod(zy, 16)
|
|
return vector(x, y, z)
|
|
|
|
|
|
class Inventory:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@staticmethod
|
|
def from_bytes(data: bytes):
|
|
inv = Inventory()
|
|
return inv
|
|
|
|
|
|
class MetaData:
|
|
def __init__(self):
|
|
self._meta = {}
|
|
self._private = set()
|
|
|
|
def __getitem__(self, key: bytes):
|
|
return self._meta[key]
|
|
|
|
def __setitem__(self, key: bytes, value: bytes):
|
|
self._meta[key] = value
|
|
|
|
def mark_as_private(self, key: bytes, private: bool):
|
|
if private:
|
|
self._private.add(key)
|
|
else:
|
|
self._private.discard(key)
|
|
|
|
|
|
class StaticObject:
|
|
def __init__(self, type_, pos, data):
|
|
self._type = type_
|
|
self._pos = pos
|
|
self._data = data
|
|
|
|
|
|
class Timer:
|
|
def __init__(self, timeout: int, elapsed: int):
|
|
self._timeout = timeout
|
|
self._elapsed = elapsed
|
|
|
|
|
|
class MapBlock:
|
|
def __init__(self):
|
|
self._flags = 0
|
|
self._lighting_complete = 0
|
|
self._timestamp = 0
|
|
self._nodes = tuple(
|
|
tuple(
|
|
["ignore" for _ in range(MAP_BLOCKSIZE)]
|
|
for _ in range(MAP_BLOCKSIZE)
|
|
) for _ in range(MAP_BLOCKSIZE)
|
|
)
|
|
self._param1 = tuple(
|
|
tuple(
|
|
[0 for _ in range(MAP_BLOCKSIZE)]
|
|
for _ in range(MAP_BLOCKSIZE)
|
|
) for _ in range(MAP_BLOCKSIZE)
|
|
)
|
|
self._param2 = tuple(
|
|
tuple(
|
|
[0 for _ in range(MAP_BLOCKSIZE)]
|
|
for _ in range(MAP_BLOCKSIZE)
|
|
) for _ in range(MAP_BLOCKSIZE)
|
|
)
|
|
self._metadata = tuple(
|
|
tuple(
|
|
[None for _ in range(MAP_BLOCKSIZE)]
|
|
for _ in range(MAP_BLOCKSIZE)
|
|
) for _ in range(MAP_BLOCKSIZE)
|
|
)
|
|
self._inventory = tuple(
|
|
tuple(
|
|
[None for _ in range(MAP_BLOCKSIZE)]
|
|
for _ in range(MAP_BLOCKSIZE)
|
|
) for _ in range(MAP_BLOCKSIZE)
|
|
)
|
|
self._timer = tuple(
|
|
tuple(
|
|
[None for _ in range(MAP_BLOCKSIZE)]
|
|
for _ in range(MAP_BLOCKSIZE)
|
|
) for _ in range(MAP_BLOCKSIZE)
|
|
)
|
|
|
|
def iter_nodes(self):
|
|
for plane in self._nodes:
|
|
for row in plane:
|
|
yield from row
|
|
|
|
@staticmethod
|
|
def import_from_serialized(serialized_data: bytes):
|
|
mapblock = MapBlock()
|
|
version = serialized_data[0] # struct.unpack('>b', serialized_data)
|
|
if version != 29:
|
|
raise RuntimeError(f'can\'t parse version {version}')
|
|
|
|
stream = StreamReader(pyzstd.decompress(serialized_data[1:]))
|
|
mapblock._flags = stream.u8()
|
|
mapblock._lighting_complete = stream.u16()
|
|
mapblock._timestamp = stream.u32()
|
|
name_id_mapping_version = stream.u8()
|
|
num_name_id_mappings = stream.u16()
|
|
|
|
if name_id_mapping_version != 0:
|
|
raise RuntimeError(f'can\'t grok name_id_mapping_version {name_id_mapping_version}')
|
|
|
|
name_by_id = {}
|
|
for _ in range(num_name_id_mappings):
|
|
id_ = stream.u16()
|
|
name_len = stream.u16()
|
|
|
|
name_by_id[id_] = stream.bytes(name_len)
|
|
|
|
content_width = stream.u8()
|
|
if content_width != 2:
|
|
raise RuntimeError(f'invalid content_width {content_width}')
|
|
|
|
params_width = stream.u8()
|
|
|
|
if params_width != 2:
|
|
raise RuntimeError(f'invalid params_width {params_width}')
|
|
|
|
for z in range(MAP_BLOCKSIZE):
|
|
for y in range(MAP_BLOCKSIZE):
|
|
for x in range(MAP_BLOCKSIZE):
|
|
mapblock._nodes[z][y][x] = name_by_id[stream.u16()]
|
|
|
|
for z in range(MAP_BLOCKSIZE):
|
|
for y in range(MAP_BLOCKSIZE):
|
|
for x in range(MAP_BLOCKSIZE):
|
|
mapblock._param1[z][y][x] = stream.u8()
|
|
|
|
for z in range(MAP_BLOCKSIZE):
|
|
for y in range(MAP_BLOCKSIZE):
|
|
for x in range(MAP_BLOCKSIZE):
|
|
mapblock._param2[z][y][x] = stream.u8()
|
|
|
|
ib = ''
|
|
node_metadata_version = stream.u8()
|
|
if node_metadata_version > 0:
|
|
|
|
if node_metadata_version != 2:
|
|
raise RuntimeError(f'unexpected node_metadata_version {node_metadata_version}')
|
|
|
|
node_metadata_count = stream.u16()
|
|
|
|
for _ in range(node_metadata_count):
|
|
pos = unpack_pos(stream.u16())
|
|
meta = MetaData()
|
|
num_vars = stream.u32()
|
|
for _ in range(num_vars):
|
|
key_len = stream.u16()
|
|
key = stream.bytes(key_len)
|
|
val_len = stream.u32()
|
|
meta[key] = stream.bytes(val_len)
|
|
meta.mark_as_private(key, stream.u8() == 1)
|
|
|
|
mapblock._metadata[pos.z][pos.y][pos.x] = meta
|
|
mapblock._inventory[pos.z][pos.y][pos.x] = Inventory.from_bytes(stream.inventory_bytes())
|
|
|
|
static_object_version = stream.u8()
|
|
|
|
if static_object_version != 0:
|
|
raise RuntimeError(f'unexpected static_object_version {static_object_version} {ib} {stream._data}')
|
|
|
|
static_object_count = stream.u16()
|
|
static_objects = []
|
|
|
|
for _ in range(static_object_count):
|
|
type_ = stream.u8()
|
|
pos_x_nodes = stream.s32() / 1e5
|
|
pos_y_nodes = stream.s32() / 1e5
|
|
pos_z_nodes = stream.s32() / 1e5
|
|
data_size = stream.u16()
|
|
data = stream.bytes(data_size)
|
|
static_objects.append(StaticObject(type_, vector(pos_x_nodes, pos_y_nodes, pos_z_nodes), data))
|
|
|
|
timers_length = stream.u8()
|
|
if timers_length != 10:
|
|
raise RuntimeError(f'unexpected timers_length {timers_length}')
|
|
num_of_timers = stream.u16()
|
|
for _ in range(num_of_timers):
|
|
pos = unpack_pos(stream.u16())
|
|
timeout = stream.s32()
|
|
elapsed = stream.s32()
|
|
mapblock._timer[pos.z][pos.y][pos.x] = Timer(timeout, elapsed)
|
|
|
|
return mapblock
|
|
|
|
|
|
class MapBlockSimple:
|
|
def __init__(self):
|
|
self.node_names = []
|
|
|
|
@staticmethod
|
|
def import_from_serialized(serialized_data: bytes):
|
|
mapblock = MapBlockSimple()
|
|
version = serialized_data[0]
|
|
if type(version) is bytes:
|
|
version = ord(version)
|
|
if version != 29:
|
|
raise RuntimeError(f'can\'t parse version {version}')
|
|
|
|
stream = StreamReader(pyzstd.decompress(serialized_data[1:]))
|
|
stream.u8() # flags
|
|
stream.u16() # lighting_complete
|
|
stream.u32() # timestamp
|
|
name_id_mapping_version = stream.u8()
|
|
num_name_id_mappings = stream.u16()
|
|
|
|
if name_id_mapping_version != 0:
|
|
raise RuntimeError(f'can\'t grok name_id_mapping_version {name_id_mapping_version}')
|
|
|
|
for _ in range(num_name_id_mappings):
|
|
stream.u16() # id
|
|
name_len = stream.u16()
|
|
mapblock.node_names.append(stream.bytes(name_len))
|
|
|
|
return mapblock
|