pyCraft/minecraft/networking/types/basic.py

380 lines
10 KiB
Python
Raw Normal View History

"""Contains definitions for minecraft's different data types
Each type has a method which is used to read and write it.
These definitions and methods are used by the packet definitions
"""
import struct
2015-09-05 09:32:27 +02:00
import uuid
import io
import pynbt
from .utility import Vector, class_and_instancemethod
__all__ = (
'Type', 'Boolean', 'UnsignedByte', 'Byte', 'Short', 'UnsignedShort',
'Integer', 'FixedPointInteger', 'Angle', 'VarInt', 'VarLong', 'Long',
'UnsignedLong', 'Float', 'Double', 'ShortPrefixedByteArray',
'VarIntPrefixedByteArray', 'TrailingByteArray', 'String', 'UUID',
'Position', 'NBT', 'PrefixedArray',
)
class Type(object):
# pylint: disable=no-self-argument
__slots__ = ()
@class_and_instancemethod
def read_with_context(cls_or_self, file_object, _context):
return cls_or_self.read(file_object)
@class_and_instancemethod
def send_with_context(cls_or_self, value, socket, _context):
return cls_or_self.send(value, socket)
@classmethod
def read(cls, file_object):
if cls.read_with_context == Type.read_with_context:
raise NotImplementedError('One of "read" or "read_with_context" '
'must be overridden in a subclass.')
else:
raise TypeError('This type requires a ConnectionContext: '
'call "read_with_context" instead of "read".')
@classmethod
def send(cls, value, socket):
if cls.send_with_context == Type.send_with_context:
raise NotImplementedError('One of "send" or "send_with_context" '
'must be overridden in a subclass.')
else:
raise TypeError('This type requires a ConnectionContext: '
'call "send_with_context" instead of "send".')
2014-10-08 19:12:37 +02:00
class Boolean(Type):
@staticmethod
def read(file_object):
return struct.unpack('?', file_object.read(1))[0]
@staticmethod
def send(value, socket):
socket.send(struct.pack('?', value))
class UnsignedByte(Type):
@staticmethod
def read(file_object):
return struct.unpack('>B', file_object.read(1))[0]
@staticmethod
def send(value, socket):
socket.send(struct.pack('>B', value))
class Byte(Type):
@staticmethod
def read(file_object):
return struct.unpack('>b', file_object.read(1))[0]
@staticmethod
def send(value, socket):
socket.send(struct.pack('>b', value))
class Short(Type):
@staticmethod
def read(file_object):
return struct.unpack('>h', file_object.read(2))[0]
@staticmethod
def send(value, socket):
socket.send(struct.pack('>h', value))
class UnsignedShort(Type):
@staticmethod
def read(file_object):
return struct.unpack('>H', file_object.read(2))[0]
@staticmethod
def send(value, socket):
socket.send(struct.pack('>H', value))
class Integer(Type):
@staticmethod
def read(file_object):
return struct.unpack('>i', file_object.read(4))[0]
@staticmethod
def send(value, socket):
socket.send(struct.pack('>i', value))
class FixedPointInteger(Type):
@staticmethod
def read(file_object):
return Integer.read(file_object) / 32
@staticmethod
def send(value, socket):
Integer.send(int(value * 32), socket)
class Angle(Type):
@staticmethod
def read(file_object):
# Linearly transform angle in steps of 1/256 into steps of 1/360
return 360 * UnsignedByte.read(file_object) / 256
@staticmethod
def send(value, socket):
# Normalize angle between 0 and 255 and convert to int.
UnsignedByte.send(round(256 * ((value % 360) / 360)), socket)
class VarInt(Type):
max_bytes = 5
@classmethod
def read(cls, file_object):
2015-04-02 19:02:47 +02:00
number = 0
# Limit of 'cls.max_bytes' bytes, otherwise its possible to cause
# a DOS attack by sending VarInts that just keep going
2017-07-16 10:00:14 +02:00
bytes_encountered = 0
while True:
2016-06-17 23:54:21 +02:00
byte = file_object.read(1)
if len(byte) < 1:
raise EOFError("Unexpected end of message.")
2017-07-16 10:00:14 +02:00
2016-06-17 23:54:21 +02:00
byte = ord(byte)
2017-07-16 10:00:14 +02:00
number |= (byte & 0x7F) << 7 * bytes_encountered
2015-04-02 19:02:47 +02:00
if not byte & 0x80:
break
2017-07-16 10:00:14 +02:00
bytes_encountered += 1
if bytes_encountered > cls.max_bytes:
2017-07-16 10:00:14 +02:00
raise ValueError("Tried to read too long of a VarInt")
2015-04-02 19:02:47 +02:00
return number
@staticmethod
def send(value, socket):
2015-04-03 05:04:59 +02:00
out = bytes()
while True:
byte = value & 0x7F
value >>= 7
out += struct.pack("B", byte | (0x80 if value > 0 else 0))
if value == 0:
break
socket.send(out)
@staticmethod
def size(value):
for max_value, size in VARINT_SIZE_TABLE.items():
if value < max_value:
return size
2017-07-16 10:00:14 +02:00
raise ValueError("Integer too large")
class VarLong(VarInt):
max_bytes = 10
# Maps (maximum integer value -> size of VarInt in bytes)
2015-04-02 19:02:47 +02:00
VARINT_SIZE_TABLE = {
2 ** 7: 1,
2 ** 14: 2,
2 ** 21: 3,
2 ** 28: 4,
2 ** 35: 5,
2 ** 42: 6,
2 ** 49: 7,
2 ** 56: 8,
2 ** 63: 9,
2 ** 70: 10,
2 ** 77: 11,
2 ** 84: 12
}
class Long(Type):
@staticmethod
def read(file_object):
return struct.unpack('>q', file_object.read(8))[0]
@staticmethod
def send(value, socket):
socket.send(struct.pack('>q', value))
2017-08-05 01:49:16 +02:00
2017-08-02 16:21:00 +02:00
class UnsignedLong(Type):
@staticmethod
def read(file_object):
return struct.unpack('>Q', file_object.read(8))[0]
@staticmethod
def send(value, socket):
socket.send(struct.pack('>Q', value))
2017-08-05 01:49:16 +02:00
class Float(Type):
@staticmethod
def read(file_object):
return struct.unpack('>f', file_object.read(4))[0]
@staticmethod
def send(value, socket):
socket.send(struct.pack('>f', value))
2014-10-08 19:12:37 +02:00
class Double(Type):
@staticmethod
def read(file_object):
return struct.unpack('>d', file_object.read(8))[0]
@staticmethod
def send(value, socket):
socket.send(struct.pack('>d', value))
class ShortPrefixedByteArray(Type):
@staticmethod
def read(file_object):
length = Short.read(file_object)
return struct.unpack(str(length) + "s", file_object.read(length))[0]
@staticmethod
def send(value, socket):
Short.send(len(value), socket)
socket.send(value)
class VarIntPrefixedByteArray(Type):
@staticmethod
def read(file_object):
length = VarInt.read(file_object)
return struct.unpack(str(length) + "s", file_object.read(length))[0]
@staticmethod
def send(value, socket):
VarInt.send(len(value), socket)
socket.send(struct.pack(str(len(value)) + "s", value))
class TrailingByteArray(Type):
""" A byte array consisting of all remaining data. If present in a packet
definition, this should only be the type of the last field. """
@staticmethod
def read(file_object):
return file_object.read()
@staticmethod
def send(value, socket):
socket.send(value)
class String(Type):
@staticmethod
def read(file_object):
length = VarInt.read(file_object)
return file_object.read(length).decode("utf-8")
@staticmethod
def send(value, socket):
2015-04-03 05:51:53 +02:00
value = value.encode('utf-8')
VarInt.send(len(value), socket)
socket.send(value)
2015-09-05 09:32:27 +02:00
2015-09-05 09:32:27 +02:00
class UUID(Type):
@staticmethod
def read(file_object):
return str(uuid.UUID(bytes=file_object.read(16)))
2017-07-16 10:00:14 +02:00
@staticmethod
def send(value, socket):
socket.send(uuid.UUID(value).bytes)
2017-08-02 16:21:00 +02:00
2017-08-05 01:49:16 +02:00
class Position(Type, Vector):
"""3D position vectors with a specific, compact network representation."""
__slots__ = ()
2017-08-02 16:21:00 +02:00
@staticmethod
def read_with_context(file_object, context):
2017-08-05 01:49:16 +02:00
location = UnsignedLong.read(file_object)
x = int(location >> 38) # 26 most significant bits
if context.protocol_version >= 443:
z = int((location >> 12) & 0x3FFFFFF) # 26 intermediate bits
y = int(location & 0xFFF) # 12 least signficant bits
else:
y = int((location >> 26) & 0xFFF) # 12 intermediate bits
z = int(location & 0x3FFFFFF) # 26 least significant bits
2017-08-02 16:21:00 +02:00
2017-08-05 01:49:16 +02:00
if x >= pow(2, 25):
x -= pow(2, 26)
2017-08-02 16:21:00 +02:00
2017-08-05 01:49:16 +02:00
if y >= pow(2, 11):
y -= pow(2, 12)
2017-08-02 16:21:00 +02:00
2017-08-05 01:49:16 +02:00
if z >= pow(2, 25):
z -= pow(2, 26)
2017-08-02 16:21:00 +02:00
return Position(x=x, y=y, z=z)
2017-08-02 16:21:00 +02:00
@staticmethod
def send_with_context(position, socket, context):
# 'position' can be either a tuple or Position object.
x, y, z = position
value = ((x & 0x3FFFFFF) << 38 | (z & 0x3FFFFFF) << 12 | (y & 0xFFF)
if context.protocol_version >= 443 else
(x & 0x3FFFFFF) << 38 | (y & 0xFFF) << 26 | (z & 0x3FFFFFF))
2017-08-02 16:21:00 +02:00
UnsignedLong.send(value, socket)
class NBT(Type):
@staticmethod
def read(file_object):
return pynbt.NBTFile(io=file_object)
@staticmethod
def send(value, socket):
buffer = io.BytesIO()
pynbt.NBTFile(value=value).save(buffer)
socket.send(buffer.getvalue())
class PrefixedArray(Type):
__slots__ = 'length_type', 'element_type'
def __init__(self, length_type, element_type):
self.length_type = length_type
self.element_type = element_type
def read(self, file_object):
return self.__read(file_object, self.element_type.read)
def send(self, value, socket):
return self.__send(value, socket, self.element_type.send)
def read_with_context(self, file_object, context):
def element_read(file_object):
return self.element_type.read_with_context(file_object, context)
return self.__read(file_object, element_read)
def send_with_context(self, value, socket, context):
def element_send(value, socket):
return self.element_type.send_with_context(value, socket, context)
return self.__send(value, socket, element_send)
def __read(self, file_object, element_read):
length = self.length_type.read(file_object)
return [element_read(file_object) for i in range(length)]
def __send(self, value, socket, element_send):
self.length_type.send(len(value), socket)
for element in value:
element_send(element, socket)