Minetest-mapshrink-redis/mt_map_redis_parser.py

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))