365 lines
13 KiB
Python
365 lines
13 KiB
Python
#!/usr/bin/env python
|
|
|
|
#Licence LGPL v2.1
|
|
#Created for version 25
|
|
#https://github.com/minetest/minetest/blob/944ffe9e532a3b2be686ef28c33313148760b1c9/doc/mapformat.txt
|
|
|
|
# Original Author: lag01 administrator of the JustTest MineTest server
|
|
# Edited by 843jdc
|
|
|
|
|
|
# PURPOSE:
|
|
# Parses a MineTest map of requested data called by another program.
|
|
|
|
# For some reason, I have to use "\xNUM", where NUM = a number, to be able to interpret
|
|
# what is read from a Redis database file.
|
|
# I don't know (yet!) what "\xNUM" is doing other than making this program work.
|
|
#
|
|
# int(binascii.hexlify(A), 16) -- Converts a single byte represented in '\x' notation
|
|
# into a proper integer that has a range of 0-255.
|
|
#
|
|
# When x is an integer, x == num is valid
|
|
# When x is a string, x == \xnum is valid
|
|
# Can use type(x) to show how x is stored: int or str
|
|
# This means that Redislite is reading int as strings!
|
|
# But it is also adding '\x' to the number string. Maybe. If so, why?
|
|
#
|
|
# To convert a byte read from Redis in \xnum format into a two character string, use:
|
|
# binascii.hexlify(string_byte_to_convert)
|
|
#
|
|
# To convert a byte read from Redis in \xnum format into an integer, use:
|
|
# int(binascii.hexlify(string_byte_to_convert), 16)
|
|
|
|
# NOTE: For all I know, I could have used the original struct.unpack commands
|
|
# But this method works! So I'm not testing that right now
|
|
|
|
#import struct
|
|
import zlib
|
|
import binascii
|
|
|
|
# It seems that this program doesn't inherit the Verbose variable from countowners.py
|
|
Verbose = 0
|
|
|
|
class MtRedisParser:
|
|
'Allows to read data from a Minetest map block in a Redis database file'
|
|
versionInt = 25
|
|
flagsInt = None
|
|
lightingInt = None
|
|
content_widthInt = 2
|
|
error = 0
|
|
params_widthInt = 2
|
|
length = None
|
|
static_object_version = 0
|
|
static_object_count = None
|
|
objectsRead = ''
|
|
timestamp = None
|
|
name_id_mapping_version = 0
|
|
num_name_id_mappings = None
|
|
nameIdMappingsRead = ''
|
|
nameIdMappings = None
|
|
nodeDataRead = '' # Uncompressed node data
|
|
nodeMetadataRead = '' # Uncompressed metadata for items in a mapblock.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
arrayParam0 = None # Array of param0 values indexed by position
|
|
arrayParam1 = None # Array of param1 values
|
|
arrayParam2 = None # Array of param2 values
|
|
|
|
|
|
metadata_version = 1 # Always 1.
|
|
numNodesWMeta = 0 # Number of nodes within a mapblock that have metadata.
|
|
arrayPosition = None # Array: Position of each node with metadata.
|
|
arrayNumVars = None # Array: Number of how many Key|Value pairs each node has.
|
|
arrayKeys = None # Array: Key names for each nodes' variables. Example: owner
|
|
arrayValues = None # Array: Key values for each nodes' variables. Example: Admin
|
|
inventoryBuf = None
|
|
arraySI = None # Array: Serialized Inventory for a node.
|
|
arrayInventoryList = None # Array: List of items in a nodes' inventory
|
|
|
|
length_of_timer = 10
|
|
num_of_timers = None
|
|
timersRead = ''
|
|
arrayTimerTimeout = None
|
|
arrayTimerElapsed = None
|
|
|
|
# Not used because of my changes :
|
|
arrayMetadataTypeId = None
|
|
arrayMetadataRead = None
|
|
|
|
|
|
|
|
|
|
def __init__(self, block):
|
|
cursor = 0
|
|
self.MappingID = {}
|
|
self.nameIdMappings = {}
|
|
|
|
|
|
|
|
|
|
self.arrayParam0 = {}
|
|
self.arrayParam1 = {}
|
|
self.arrayParam2 = {}
|
|
self.numNodesWMeta = 0
|
|
self.arrayPosition = {}
|
|
self.arrayNumVars = {}
|
|
self.arrayKeys = {}
|
|
self.arrayValues = {}
|
|
self.inventoryBuf = {}
|
|
self.arraySI = {}
|
|
self.arrayTimerTimeout = {}
|
|
self.arrayTimerElapsed = {}
|
|
self.length = len(block)
|
|
self.MappingIDLen = {}
|
|
self.MappingNames= {}
|
|
self.nameIdMappingsRead = ""
|
|
# Not used because of my changes :
|
|
self.arrayMetadataTypeId = {}
|
|
self.arrayMetadataRead = {}
|
|
self.arrayInventoryList = {}
|
|
|
|
|
|
# Map Format Version Number (unsigned char = 1 byte)
|
|
# Ref serialization.h
|
|
self.versionInt = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
|
|
|
|
# Flags (unsigned char = 1 byte)
|
|
self.flagsInt = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
|
|
|
|
# Version 27 adds a u16 here for lighting. (unsigned short = 2 bytes)
|
|
if self.versionInt >= 27:
|
|
i = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
j = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
self.lightingInt = (i * 256) + j
|
|
|
|
|
|
# Content Width (unsigned char = 1 byte)
|
|
self.content_widthInt = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
if self.versionInt <= 23 and self.content_widthInt != 1:
|
|
print("Content width is wrong for this version!")
|
|
self.error = 1
|
|
return
|
|
if self.versionInt >= 24 and self.content_widthInt != 2:
|
|
print("Content width is wrong for this version!")
|
|
self.error = 1
|
|
return
|
|
cursor+= 1
|
|
|
|
|
|
# Params Width (unsigned char = 1 byte)
|
|
self.params_widthInt = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
if self.params_widthInt != 2:
|
|
print("Params width not supported!")
|
|
self.error = 1
|
|
return
|
|
cursor+= 1
|
|
|
|
|
|
# Node Data - compressed
|
|
try:
|
|
decompressor = zlib.decompressobj()
|
|
self.nodeDataRead = decompressor.decompress( block[cursor:] )
|
|
cursor = self.length - len(decompressor.unused_data)
|
|
except:
|
|
self.error = 1
|
|
return
|
|
|
|
|
|
# Node Metadata - compressed
|
|
try:
|
|
decompressor = zlib.decompressobj()
|
|
self.nodeMetadataRead = decompressor.decompress( block[cursor:] )
|
|
cursor = self.length - len(decompressor.unused_data)
|
|
except:
|
|
self.error = 1
|
|
return
|
|
|
|
|
|
# Node timers are here only if map version is less than 25
|
|
# If map version is 25 or greater, node timers are serialized later
|
|
if self.versionInt <= 24:
|
|
print("Node Timer parser for this map version is not written yet")
|
|
self.error = 1
|
|
return
|
|
|
|
# Static Objects
|
|
# Read static object version (unsigned char = 1 byte)
|
|
self.static_object_version = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
if self.static_object_version != 0:
|
|
print("Static Object version not supported!")
|
|
self.error = 1
|
|
return
|
|
cursor+=1
|
|
|
|
# Read static objects. Store data in self.objectsRead. Do not parse it.
|
|
# Read static object count (unsigned short = 2 bytes)
|
|
i = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
j = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
self.static_object_count = (i * 256) + j
|
|
for i in range(0, self.static_object_count):
|
|
# Read each static objects's type (unsigned char = 1 byte)
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
# Read each static objects's x,y,z position (signed int = 4 bytes)
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
# Read each static object's y position (signed int = 4 bytes)
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
# Read each static object's y position (signed int = 4 bytes)
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
|
|
# Read the data size (unsigned short = 2 bytes)
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
k = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
|
|
# Determine the size of the data to be read
|
|
data_size = (int(j, 16) * 256) + int(k, 16)
|
|
for l in range(0, data_size):
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
self.objectsRead+= j
|
|
cursor+= 1
|
|
|
|
# Timestamp Read unsigned int (4 bytes)
|
|
i = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
j = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
k = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
l = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
self.timestamp = ( i * 16777216) + (j * 65536) + (k * 256) + l
|
|
|
|
|
|
# Name-id mappings.
|
|
# Check version
|
|
# Read each name-id mapping.
|
|
#
|
|
# Modifies:
|
|
# self.name_id_mapping_version INT Name ID Mapping Version Number
|
|
# self.num_name_id_mappings INT Number of different nodes in this map block
|
|
# self.MappingID[] INT Mapping ID Number of each different node
|
|
# self.MappingNames[] STR List of the names of the different nodes
|
|
# self.nameIdMappingsRead[] STR Concantenated list of the names of the different nodes
|
|
|
|
# Check name-id-mapping version number (unsigned char = 1 byte)
|
|
self.name_id_mapping_version = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
if self.name_id_mapping_version != 0:
|
|
print("Name-ID mapping version not supported!")
|
|
# self.error = 1
|
|
# return
|
|
|
|
# Read and store the number of name-id mappings (unsigned short = 2 bytes)
|
|
i = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
j = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
self.num_name_id_mappings = (i * 256) + j
|
|
|
|
# Loop through the number of name-id mappings
|
|
# Store each ID number, its name, add it to list
|
|
for i in range(0, self.num_name_id_mappings):
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
cursor+= 1
|
|
k = binascii.hexlify(block[cursor:cursor + 1])
|
|
cursor+= 1
|
|
itemID = (int(j, 16) * 256) + int(k, 16)
|
|
self.MappingID[i] = itemID
|
|
|
|
j = binascii.hexlify(block[cursor:cursor + 1])
|
|
cursor+= 1
|
|
k = binascii.hexlify(block[cursor:cursor + 1])
|
|
cursor+= 1
|
|
leng = (int(j, 16) * 256) + int(k, 16)
|
|
m = ""
|
|
for l in range(0, leng):
|
|
j = chr(int(binascii.hexlify(block[cursor:cursor + 1]), 16))
|
|
m = m + j
|
|
cursor+= 1
|
|
self.nameIdMappingsRead = self.nameIdMappingsRead + m
|
|
self.MappingNames[i] = m
|
|
|
|
|
|
|
|
# For map version 25 only:
|
|
# Timers - Read each timer blob. Do not parse them
|
|
# Store the data in timersRead to possibly be parsed later.
|
|
# Read (unsigned char = 1 byte)
|
|
if self.versionInt >= 25:
|
|
self.length_of_timer = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
# Read num_of_timers (unsigned short = 2 bytes)
|
|
cursor+= 1
|
|
i = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
j = int(binascii.hexlify(block[cursor:cursor + 1]), 16)
|
|
cursor+= 1
|
|
self.num_of_timers = (i * 256) + j
|
|
# Read each node timer
|
|
for i in range(0, self.num_of_timers):
|
|
self.timersRead+= binascii.hexlify(block[cursor:cursor + 10])
|
|
cursor+= 10
|
|
if self.length != cursor:
|
|
print("Parsed length is wrong!")
|
|
|
|
if Verbose == 1:
|
|
print("Block Version Number = " + str(self.versionInt))
|
|
print("Timestamp: " + str(self.timestamp))
|
|
print("Flags: " + str(self.flagsInt))
|
|
if self.versionInt >= 27:
|
|
print("Lighting")
|
|
print("Num static objects: " + str(self.static_object_count))
|
|
print("num_name_id_mappings = " + str(self.num_name_id_mappings))
|
|
print("ID: " + str(itemID) + " Name: " + m)
|
|
print("Concantenated Names: " + self.nameIdMappingsRead)
|
|
print("Number of timers: " + str(self.num_of_timers))
|