Merge branch 'v1.16' into master

This commit is contained in:
joo 2020-08-19 20:27:11 +02:00
commit 3c84c2a429
33 changed files with 830 additions and 330 deletions

3
.gitignore vendored
View File

@ -81,5 +81,8 @@ target/
# sftp configuration file
sftp-config.json
### Visual Studio
.vscode
### pyCraft ###
credentials

View File

@ -3,9 +3,7 @@ python: 3.8
matrix:
include:
- python: 2.7
env: TOX_ENV=py27
- python: pypy
- python: pypy3
env: TOX_ENV=pypy
- python: 3.5
env: TOX_ENV=py35

View File

@ -31,6 +31,7 @@ pyCraft is compatible with the following Minecraft releases:
* 1.13, 1.13.1, 1.13.2
* 1.14, 1.14.1, 1.14.2, 1.14.3, 1.14.4
* 1.15, 1.15.1, 1.15.2
* 1.16, 1.16.1, 1.16.2
In addition, some development snapshots and pre-release versions are supported:
`<minecraft/__init__.py>`_ contains a full list of supported Minecraft versions
@ -50,7 +51,6 @@ Supported Python versions
-------------------------
pyCraft is compatible with (at least) the following Python implementations:
* Python 2.7
* Python 3.5
* Python 3.6
* Python 3.7
@ -61,7 +61,7 @@ Requirements
------------
- `cryptography <https://github.com/pyca/cryptography#cryptography>`_
- `requests <http://docs.python-requests.org/en/latest/>`_
- `future <http://python-future.org/>`_
- `PyNBT <https://github.com/TkTech/PyNBT>`_
The requirements are also stored in ``setup.py``

View File

@ -4,7 +4,7 @@ with a MineCraft server.
"""
# The version number of the most recent pyCraft release.
__version__ = "0.6.0"
__version__ = "0.7.0"
# A dict mapping the ID string of each Minecraft version supported by pyCraft
# to the corresponding protocol version number. The ID string of a version is
@ -225,20 +225,84 @@ SUPPORTED_MINECRAFT_VERSIONS = {
'1.15.2-pre1': 576,
'1.15.2-pre2': 577,
'1.15.2': 578,
'20w06a': 701,
'20w07a': 702,
'20w08a': 703,
'20w09a': 704,
'20w10a': 705,
'20w11a': 706,
'20w12a': 707,
'20w13a': 708,
'20w13b': 709,
'20w14a': 710,
'20w15a': 711,
'20w16a': 712,
'20w17a': 713,
'20w18a': 714,
'20w19a': 715,
'20w20a': 716,
'20w20b': 717,
'20w21a': 718,
'20w22a': 719,
'1.16-pre1': 721,
'1.16-pre2': 722,
'1.16-pre3': 725,
'1.16-pre4': 727,
'1.16-pre5': 729,
'1.16-pre6': 730,
'1.16-pre7': 732,
'1.16-pre8': 733,
'1.16-rc1': 734,
'1.16': 735,
'1.16.1': 736,
'20w27a': 738,
'20w28a': 740,
'20w29a': 741,
'20w30a': 743,
'1.16.2-pre1': 744,
'1.16.2-pre2': 746,
'1.16.2-pre3': 748,
'1.16.2-rc1': 749,
'1.16.2-rc2': 750,
'1.16.2': 751,
}
# Those Minecraft versions supported by pyCraft which are "release" versions,
# i.e. not development snapshots or pre-release versions.
RELEASE_MINECRAFT_VERSIONS = {
vid: protocol for (vid, protocol) in SUPPORTED_MINECRAFT_VERSIONS.items()
if __import__('re').match(r'\d+(\.\d+)+$', vid)}
RELEASE_MINECRAFT_VERSIONS = {}
# The protocol versions of SUPPORTED_MINECRAFT_VERSIONS, without duplicates,
# in ascending numerical (and hence chronological) order.
SUPPORTED_PROTOCOL_VERSIONS = \
sorted(set(SUPPORTED_MINECRAFT_VERSIONS.values()))
SUPPORTED_PROTOCOL_VERSIONS = []
# The protocol versions of RELEASE_MINECRAFT_VERSIONS, without duplicates,
# in ascending numerical (and hence chronological) order.
RELEASE_PROTOCOL_VERSIONS = \
sorted(set(RELEASE_MINECRAFT_VERSIONS.values()))
RELEASE_PROTOCOL_VERSIONS = []
def initglobals():
'''Init the globals from the SUPPORTED_MINECRAFT_VERSIONS dict
This allows the SUPPORTED_MINECRAFT_VERSIONS dict to be updated, then the
other globals can be updated as well, to allow for dynamic version support.
All updates are done by reference to allow this to work else where in the
code.
'''
global RELEASE_MINECRAFT_VERSIONS, SUPPORTED_PROTOCOL_VERSIONS
global RELEASE_PROTOCOL_VERSIONS
import re
for (vid, protocol) in SUPPORTED_MINECRAFT_VERSIONS.items():
if re.match(r"\d+(\.\d+)+$", vid):
RELEASE_MINECRAFT_VERSIONS[vid] = protocol
if protocol not in RELEASE_PROTOCOL_VERSIONS:
RELEASE_PROTOCOL_VERSIONS.append(protocol)
if protocol not in SUPPORTED_PROTOCOL_VERSIONS:
SUPPORTED_PROTOCOL_VERSIONS.append(protocol)
SUPPORTED_PROTOCOL_VERSIONS.sort()
RELEASE_PROTOCOL_VERSIONS.sort()
initglobals()

View File

@ -1,24 +0,0 @@
"""
This module stores code used for making pyCraft compatible with
both Python2 and Python3 while using the same codebase.
"""
# Raw input -> input shenangians
# example
# > from minecraft.compat import input
# > input("asd")
# Hi, I'm pylint, and sometimes I act silly, at which point my programmer
# overlords need to correct me.
# pylint: disable=undefined-variable,redefined-builtin,invalid-name
try:
input = raw_input
except NameError:
input = input
try:
unicode = unicode
except NameError:
unicode = str
# pylint: enable=undefined-variable,redefined-builtin,invalid-name

View File

@ -1,5 +1,3 @@
from __future__ import print_function
from collections import deque
from threading import RLock
import zlib
@ -11,8 +9,6 @@ import sys
import json
import re
from future.utils import raise_
from .types import VarInt
from .packets import clientbound, serverbound
from . import packets
@ -495,7 +491,8 @@ class Connection(object):
# If allowed by the final exception handler, re-raise the exception.
if self.handle_exception is None and not caught:
raise_(*exc_info)
exc_value, exc_tb = exc_info[1:]
raise exc_value.with_traceback(exc_tb)
def _version_mismatch(self, server_protocol=None, server_version=None):
if server_protocol is None:
@ -510,7 +507,10 @@ class Connection(object):
ss = 'supported, but not allowed for this connection' \
if server_protocol in SUPPORTED_PROTOCOL_VERSIONS \
else 'not supported'
raise VersionMismatch("Server's %s is %s." % (vs, ss))
err = VersionMismatch("Server's %s is %s." % (vs, ss))
err.server_protocol = server_protocol
err.server_version = server_version
raise err
def _handle_exit(self):
if not self.connected and self.handle_exit is not None:
@ -593,7 +593,8 @@ class NetworkingThread(threading.Thread):
exc_info = None
if exc_info is not None:
raise_(*exc_info)
exc_value, exc_tb = exc_info[1:]
raise exc_value.with_traceback(exc_tb)
class PacketReactor(object):
@ -644,14 +645,16 @@ class PacketReactor(object):
packet_id = VarInt.read(packet_data)
# If we know the structure of the packet, attempt to parse it
# otherwise just skip it
# otherwise, just return an instance of the base Packet class.
if packet_id in self.clientbound_packets:
packet = self.clientbound_packets[packet_id]()
packet.context = self.connection.context
packet.read(packet_data)
return packet
else:
return packets.Packet(context=self.connection.context)
packet = packets.Packet()
packet.context = self.connection.context
packet.id = packet_id
return packet
else:
return None

View File

@ -1,7 +1,7 @@
from minecraft.networking.packets import Packet
from minecraft.networking.types import (
VarInt, String, VarIntPrefixedByteArray, TrailingByteArray
VarInt, String, VarIntPrefixedByteArray, TrailingByteArray, UUID,
)
@ -54,9 +54,10 @@ class LoginSuccessPacket(Packet):
0x02
packet_name = "login success"
definition = [
{'UUID': String},
{'Username': String}]
get_definition = staticmethod(lambda context: [
{'UUID': UUID if context.protocol_version >= 707 else String},
{'Username': String}
])
class SetCompressionPacket(Packet):

View File

@ -3,10 +3,9 @@ from minecraft.networking.packets import (
)
from minecraft.networking.types import (
Integer, FixedPointInteger, Angle, UnsignedByte, Byte, Boolean, UUID,
Short, VarInt, Double, Float, String, Enum, Difficulty, Dimension,
GameMode, Long, Vector, Direction, PositionAndLook,
multi_attribute_alias,
FixedPointInteger, Angle, UnsignedByte, Byte, Boolean, UUID, Short, VarInt,
Double, Float, String, Enum, Difficulty, Long, Vector, Direction,
PositionAndLook, multi_attribute_alias,
)
from .combat_event_packet import CombatEventPacket
@ -18,6 +17,7 @@ from .block_change_packet import BlockChangePacket, MultiBlockChangePacket
from .explosion_packet import ExplosionPacket
from .sound_effect_packet import SoundEffectPacket
from .face_player_packet import FacePlayerPacket
from .join_game_and_respawn_packets import JoinGamePacket, RespawnPacket
# Formerly known as state_playing_clientbound.
@ -33,6 +33,8 @@ def get_packets(context):
DisconnectPacket,
SpawnPlayerPacket,
EntityVelocityPacket,
EntityPositionDeltaPacket,
TimeUpdatePacket,
UpdateHealthPacket,
CombatEventPacket,
ExplosionPacket,
@ -62,7 +64,9 @@ def get_packets(context):
class KeepAlivePacket(AbstractKeepAlivePacket):
@staticmethod
def get_id(context):
return 0x21 if context.protocol_version >= 550 else \
return 0x1F if context.protocol_version >= 741 else \
0x20 if context.protocol_version >= 721 else \
0x21 if context.protocol_version >= 550 else \
0x20 if context.protocol_version >= 471 else \
0x21 if context.protocol_version >= 389 else \
0x20 if context.protocol_version >= 345 else \
@ -72,41 +76,11 @@ class KeepAlivePacket(AbstractKeepAlivePacket):
0x00
class JoinGamePacket(Packet):
@staticmethod
def get_id(context):
return 0x26 if context.protocol_version >= 550 else \
0x25 if context.protocol_version >= 389 else \
0x24 if context.protocol_version >= 345 else \
0x23 if context.protocol_version >= 332 else \
0x24 if context.protocol_version >= 318 else \
0x23 if context.protocol_version >= 107 else \
0x01
packet_name = "join game"
get_definition = staticmethod(lambda context: [
{'entity_id': Integer},
{'game_mode': UnsignedByte},
{'dimension': Integer if context.protocol_version >= 108 else Byte},
{'hashed_seed': Long} if context.protocol_version >= 552 else {},
{'difficulty': UnsignedByte} if context.protocol_version < 464 else {},
{'max_players': UnsignedByte},
{'level_type': String},
{'render_distance': VarInt} if context.protocol_version >= 468 else {},
{'reduced_debug_info': Boolean},
{'respawn_screen': Boolean} if context.protocol_version >= 571 else {},
])
# These aliases declare the Enum type corresponding to each field:
Difficulty = Difficulty
GameMode = GameMode
Dimension = Dimension
class ServerDifficultyPacket(Packet):
@staticmethod
def get_id(context):
return 0x0E if context.protocol_version >= 550 else \
return 0x0D if context.protocol_version >= 721 else \
0x0E if context.protocol_version >= 550 else \
0x0D if context.protocol_version >= 332 else \
0x0E if context.protocol_version >= 318 else \
0x0D if context.protocol_version >= 70 else \
@ -125,7 +99,8 @@ class ServerDifficultyPacket(Packet):
class ChatMessagePacket(Packet):
@staticmethod
def get_id(context):
return 0x0F if context.protocol_version >= 550 else \
return 0x0E if context.protocol_version >= 721 else \
0x0F if context.protocol_version >= 550 else \
0x0E if context.protocol_version >= 343 else \
0x0F if context.protocol_version >= 332 else \
0x10 if context.protocol_version >= 317 else \
@ -133,9 +108,11 @@ class ChatMessagePacket(Packet):
0x02
packet_name = "chat message"
definition = [
get_definition = staticmethod(lambda context: [
{'json_data': String},
{'position': Byte}]
{'position': Byte},
{'sender': UUID} if context.protocol_version >= 718 else {},
])
class Position(Enum):
CHAT = 0 # A player-initiated chat message.
@ -146,7 +123,9 @@ class ChatMessagePacket(Packet):
class DisconnectPacket(Packet):
@staticmethod
def get_id(context):
return 0x1B if context.protocol_version >= 550 else \
return 0x19 if context.protocol_version >= 741 else \
0x1A if context.protocol_version >= 721 else \
0x1B if context.protocol_version >= 550 else \
0x1A if context.protocol_version >= 471 else \
0x1B if context.protocol_version >= 345 else \
0x1A if context.protocol_version >= 332 else \
@ -171,7 +150,8 @@ class SetCompressionPacket(Packet):
class SpawnPlayerPacket(Packet):
@staticmethod
def get_id(context):
return 0x05 if context.protocol_version >= 67 else \
return 0x04 if context.protocol_version >= 721 else \
0x05 if context.protocol_version >= 67 else \
0x0C
packet_name = 'spawn player'
@ -206,7 +186,9 @@ class SpawnPlayerPacket(Packet):
class EntityVelocityPacket(Packet):
@staticmethod
def get_id(context):
return 0x46 if context.protocol_version >= 550 else \
return 0x46 if context.protocol_version >= 721 else \
0x47 if context.protocol_version >= 707 else \
0x46 if context.protocol_version >= 550 else \
0x45 if context.protocol_version >= 471 else \
0x41 if context.protocol_version >= 461 else \
0x42 if context.protocol_version >= 451 else \
@ -229,10 +211,59 @@ class EntityVelocityPacket(Packet):
])
class EntityPositionDeltaPacket(Packet):
@staticmethod
def get_id(context):
return 0x27 if context.protocol_version >= 741 else \
0x28 if context.protocol_version >= 721 else \
0x29 if context.protocol_version >= 550 else \
0x28 if context.protocol_version >= 389 else \
0x27 if context.protocol_version >= 345 else \
0x26 if context.protocol_version >= 318 else \
0x25 if context.protocol_version >= 94 else \
0x26 if context.protocol_version >= 70 else \
0x15
packet_name = "entity position delta"
get_definition = staticmethod(lambda context: [
{'entity_id': VarInt},
{'delta_x': Short},
{'delta_y': Short},
{'delta_z': Short},
{'on_ground': Boolean}
])
class TimeUpdatePacket(Packet):
@staticmethod
def get_id(context):
return 0x4E if context.protocol_version >= 721 else \
0x4F if context.protocol_version >= 550 else \
0x4E if context.protocol_version >= 471 else \
0x4A if context.protocol_version >= 461 else \
0x4B if context.protocol_version >= 451 else \
0x4A if context.protocol_version >= 389 else \
0x49 if context.protocol_version >= 352 else \
0x48 if context.protocol_version >= 345 else \
0x47 if context.protocol_version >= 336 else \
0x46 if context.protocol_version >= 318 else \
0x44 if context.protocol_version >= 94 else \
0x43 if context.protocol_version >= 70 else \
0x03
packet_name = "time update"
get_definition = staticmethod(lambda context: [
{'world_age': Long},
{'time_of_day': Long},
])
class UpdateHealthPacket(Packet):
@staticmethod
def get_id(context):
return 0x49 if context.protocol_version >= 550 else \
return 0x49 if context.protocol_version >= 721 else \
0x4A if context.protocol_version >= 707 else \
0x49 if context.protocol_version >= 550 else \
0x48 if context.protocol_version >= 471 else \
0x44 if context.protocol_version >= 461 else \
0x45 if context.protocol_version >= 451 else \
@ -254,41 +285,12 @@ class UpdateHealthPacket(Packet):
])
class RespawnPacket(Packet):
@staticmethod
def get_id(context):
return 0x3B if context.protocol_version >= 550 else \
0x3A if context.protocol_version >= 471 else \
0x38 if context.protocol_version >= 461 else \
0x39 if context.protocol_version >= 451 else \
0x38 if context.protocol_version >= 389 else \
0x37 if context.protocol_version >= 352 else \
0x36 if context.protocol_version >= 345 else \
0x35 if context.protocol_version >= 336 else \
0x34 if context.protocol_version >= 332 else \
0x35 if context.protocol_version >= 318 else \
0x33 if context.protocol_version >= 70 else \
0x07
packet_name = 'respawn'
get_definition = staticmethod(lambda context: [
{'dimension': Integer},
{'difficulty': UnsignedByte} if context.protocol_version < 464 else {},
{'hashed_seed': Long} if context.protocol_version >= 552 else {},
{'game_mode': UnsignedByte},
{'level_type': String},
])
# These aliases declare the Enum type corresponding to each field:
Difficulty = Difficulty
Dimension = Dimension
GameMode = GameMode
class PluginMessagePacket(AbstractPluginMessagePacket):
@staticmethod
def get_id(context):
return 0x19 if context.protocol_version >= 550 else \
return 0x17 if context.protocol_version >= 741 else \
0x18 if context.protocol_version >= 721 else \
0x19 if context.protocol_version >= 550 else \
0x18 if context.protocol_version >= 471 else \
0x19 if context.protocol_version >= 345 else \
0x18 if context.protocol_version >= 332 else \
@ -300,7 +302,8 @@ class PluginMessagePacket(AbstractPluginMessagePacket):
class PlayerListHeaderAndFooterPacket(Packet):
@staticmethod
def get_id(context):
return 0x54 if context.protocol_version >= 550 else \
return 0x53 if context.protocol_version >= 721 else \
0x54 if context.protocol_version >= 550 else \
0x53 if context.protocol_version >= 471 else \
0x5F if context.protocol_version >= 461 else \
0x50 if context.protocol_version >= 451 else \
@ -321,7 +324,9 @@ class PlayerListHeaderAndFooterPacket(Packet):
class EntityLookPacket(Packet):
@staticmethod
def get_id(context):
return 0x2B if context.protocol_version >= 550 else \
return 0x29 if context.protocol_version >= 741 else \
0x2A if context.protocol_version >= 721 else \
0x2B if context.protocol_version >= 550 else \
0x2A if context.protocol_version >= 389 else \
0x29 if context.protocol_version >= 345 else \
0x28 if context.protocol_version >= 318 else \

View File

@ -1,14 +1,16 @@
from minecraft.networking.packets import Packet
from minecraft.networking.types import (
VarInt, Integer, UnsignedByte, Position, Vector, MutableRecord,
attribute_alias, multi_attribute_alias,
Type, VarInt, VarLong, Long, Integer, UnsignedByte, Position, Vector,
MutableRecord, PrefixedArray, Boolean, attribute_alias,
multi_attribute_alias,
)
class BlockChangePacket(Packet):
@staticmethod
def get_id(context):
return 0x0C if context.protocol_version >= 550 else \
return 0x0B if context.protocol_version >= 721 else \
0x0C if context.protocol_version >= 550 else \
0x0B if context.protocol_version >= 332 else \
0x0C if context.protocol_version >= 318 else \
0x0B if context.protocol_version >= 67 else \
@ -46,7 +48,9 @@ class BlockChangePacket(Packet):
class MultiBlockChangePacket(Packet):
@staticmethod
def get_id(context):
return 0x10 if context.protocol_version >= 550 else \
return 0x3B if context.protocol_version >= 741 else \
0x0F if context.protocol_version >= 721 else \
0x10 if context.protocol_version >= 550 else \
0x0F if context.protocol_version >= 343 else \
0x10 if context.protocol_version >= 332 else \
0x11 if context.protocol_version >= 318 else \
@ -55,12 +59,23 @@ class MultiBlockChangePacket(Packet):
packet_name = 'multi block change'
fields = 'chunk_x', 'chunk_z', 'records'
# Only used in protocol 741 and later.
class ChunkSectionPos(Vector, Type):
@classmethod
def read(cls, file_object):
value = Long.read(file_object)
x = value >> 42
z = (value >> 20) & 0x3FFFFF
y = value & 0xFFFFF
return cls(x, y, z)
# Access the 'chunk_x' and 'chunk_z' fields as a tuple.
chunk_pos = multi_attribute_alias(tuple, 'chunk_x', 'chunk_z')
@classmethod
def send(cls, pos, socket):
x, y, z = pos
value = (x & 0x3FFFFF) << 42 | (z & 0x3FFFFF) << 20 | y & 0xFFFFF
Long.send(value, socket)
class Record(MutableRecord):
class Record(MutableRecord, Type):
__slots__ = 'x', 'y', 'z', 'block_state_id'
def __init__(self, **kwds):
@ -91,30 +106,47 @@ class MultiBlockChangePacket(Packet):
# This alias is retained for backward compatibility.
blockStateId = attribute_alias('block_state_id')
def read(self, file_object):
h_position = UnsignedByte.read(file_object)
self.x, self.z = h_position >> 4, h_position & 0xF
self.y = UnsignedByte.read(file_object)
self.block_state_id = VarInt.read(file_object)
@classmethod
def read_with_context(cls, file_object, context):
record = cls()
if context.protocol_version >= 741:
value = VarLong.read(file_object)
record.block_state_id = value >> 12
record.x = (value >> 8) & 0xF
record.z = (value >> 4) & 0xF
record.y = value & 0xF
else:
h_position = UnsignedByte.read(file_object)
record.x = h_position >> 4
record.z = h_position & 0xF
record.y = UnsignedByte.read(file_object)
record.block_state_id = VarInt.read(file_object)
return record
def write(self, packet_buffer):
UnsignedByte.send(self.x << 4 | self.z & 0xF, packet_buffer)
UnsignedByte.send(self.y, packet_buffer)
VarInt.send(self.block_state_id, packet_buffer)
@classmethod
def send_with_context(self, record, socket, context):
if context.protocol_version >= 741:
value = record.block_state_id << 12 | \
(record.x & 0xF) << 8 | \
(record.z & 0xF) << 4 | \
record.y & 0xF
VarLong.send(value, socket)
else:
UnsignedByte.send(record.x << 4 | record.z & 0xF, socket)
UnsignedByte.send(record.y, socket)
VarInt.send(record.block_state_id, socket)
def read(self, file_object):
self.chunk_x = Integer.read(file_object)
self.chunk_z = Integer.read(file_object)
records_count = VarInt.read(file_object)
self.records = []
for i in range(records_count):
record = self.Record()
record.read(file_object)
self.records.append(record)
get_definition = staticmethod(lambda context: [
{'chunk_section_pos': MultiBlockChangePacket.ChunkSectionPos},
{'invert_trust_edges': Boolean}
if context.protocol_version >= 748 else {}, # Provisional field name.
{'records': PrefixedArray(VarInt, MultiBlockChangePacket.Record)},
] if context.protocol_version >= 741 else [
{'chunk_x': Integer},
{'chunk_z': Integer},
{'records': PrefixedArray(VarInt, MultiBlockChangePacket.Record)},
])
def write_fields(self, packet_buffer):
Integer.send(self.chunk_x, packet_buffer)
Integer.send(self.chunk_z, packet_buffer)
VarInt.send(len(self.records), packet_buffer)
for record in self.records:
record.write(packet_buffer)
# Access the 'chunk_x' and 'chunk_z' fields as a tuple.
# Only used prior to protocol 741.
chunk_pos = multi_attribute_alias(tuple, 'chunk_x', 'chunk_z')

View File

@ -8,7 +8,9 @@ from minecraft.networking.types import (
class CombatEventPacket(Packet):
@staticmethod
def get_id(context):
return 0x33 if context.protocol_version >= 550 else \
return 0x31 if context.protocol_version >= 741 else \
0x32 if context.protocol_version >= 721 else \
0x33 if context.protocol_version >= 550 else \
0x32 if context.protocol_version >= 471 else \
0x30 if context.protocol_version >= 451 else \
0x2F if context.protocol_version >= 389 else \

View File

@ -1,5 +1,5 @@
from minecraft.networking.types import (
Vector, Float, Byte, Integer, multi_attribute_alias,
Vector, Float, Byte, Integer, PrefixedArray, multi_attribute_alias, Type,
)
from minecraft.networking.packets import Packet
@ -7,7 +7,9 @@ from minecraft.networking.packets import Packet
class ExplosionPacket(Packet):
@staticmethod
def get_id(context):
return 0x1D if context.protocol_version >= 550 else \
return 0x1B if context.protocol_version >= 741 else \
0x1C if context.protocol_version >= 721 else \
0x1D if context.protocol_version >= 550 else \
0x1C if context.protocol_version >= 471 else \
0x1E if context.protocol_version >= 389 else \
0x1D if context.protocol_version >= 345 else \
@ -19,8 +21,27 @@ class ExplosionPacket(Packet):
packet_name = 'explosion'
fields = 'x', 'y', 'z', 'radius', 'records', \
'player_motion_x', 'player_motion_y', 'player_motion_z'
class Record(Vector, Type):
__slots__ = ()
@classmethod
def read(cls, file_object):
return cls(*(Byte.read(file_object) for i in range(3)))
@classmethod
def send(cls, record, socket):
for coord in record:
Byte.send(coord, socket)
definition = [
{'x': Float},
{'y': Float},
{'z': Float},
{'radius': Float},
{'records': PrefixedArray(Integer, Record)},
{'player_motion_x': Float},
{'player_motion_y': Float},
{'player_motion_z': Float}]
# Access the 'x', 'y', 'z' fields as a Vector tuple.
position = multi_attribute_alias(Vector, 'x', 'y', 'z')
@ -28,37 +49,3 @@ class ExplosionPacket(Packet):
# Access the 'player_motion_{x,y,z}' fields as a Vector tuple.
player_motion = multi_attribute_alias(
Vector, 'player_motion_x', 'player_motion_y', 'player_motion_z')
class Record(Vector):
__slots__ = ()
def read(self, file_object):
self.x = Float.read(file_object)
self.y = Float.read(file_object)
self.z = Float.read(file_object)
self.radius = Float.read(file_object)
records_count = Integer.read(file_object)
self.records = []
for i in range(records_count):
rec_x = Byte.read(file_object)
rec_y = Byte.read(file_object)
rec_z = Byte.read(file_object)
record = ExplosionPacket.Record(rec_x, rec_y, rec_z)
self.records.append(record)
self.player_motion_x = Float.read(file_object)
self.player_motion_y = Float.read(file_object)
self.player_motion_z = Float.read(file_object)
def write_fields(self, packet_buffer):
Float.send(self.x, packet_buffer)
Float.send(self.y, packet_buffer)
Float.send(self.z, packet_buffer)
Float.send(self.radius, packet_buffer)
Integer.send(len(self.records), packet_buffer)
for record in self.records:
Byte.send(record.x, packet_buffer)
Byte.send(record.y, packet_buffer)
Byte.send(record.z, packet_buffer)
Float.send(self.player_motion_x, packet_buffer)
Float.send(self.player_motion_y, packet_buffer)
Float.send(self.player_motion_z, packet_buffer)

View File

@ -8,7 +8,9 @@ from minecraft.networking.packets import Packet
class FacePlayerPacket(Packet):
@staticmethod
def get_id(context):
return 0x35 if context.protocol_version >= 550 else \
return 0x33 if context.protocol_version >= 741 else \
0x34 if context.protocol_version >= 721 else \
0x35 if context.protocol_version >= 550 else \
0x34 if context.protocol_version >= 471 else \
0x32 if context.protocol_version >= 451 else \
0x31 if context.protocol_version >= 389 else \

View File

@ -0,0 +1,208 @@
import pynbt
from minecraft.networking.packets import Packet
from minecraft.networking.types import (
NBT, Integer, Boolean, UnsignedByte, String, Byte, Long, VarInt,
PrefixedArray, Difficulty, GameMode, Dimension,
)
def nbt_to_snbt(tag):
'''Convert a pyNBT tag to SNBT ("stringified NBT") format.'''
scalars = {
pynbt.TAG_Byte: 'b',
pynbt.TAG_Short: 's',
pynbt.TAG_Int: '',
pynbt.TAG_Long: 'l',
pynbt.TAG_Float: 'f',
pynbt.TAG_Double: 'd',
}
if type(tag) in scalars:
return repr(tag.value) + scalars[type(tag)]
arrays = {
pynbt.TAG_Byte_Array: 'B',
pynbt.TAG_Int_Array: 'I',
pynbt.TAG_Long_Array: 'L',
}
if type(tag) in arrays:
return '[' + arrays[type(tag)] + ';' + \
','.join(map(repr, tag.value)) + ']'
if isinstance(tag, pynbt.TAG_String):
return repr(tag.value)
if isinstance(tag, pynbt.TAG_List):
return '[' + ','.join(map(nbt_to_snbt, tag.value)) + ']'
if isinstance(tag, pynbt.TAG_Compound):
return '{' + ','.join(n + ':' + nbt_to_snbt(v)
for (n, v) in tag.items()) + '}'
raise TypeError('Unknown NBT tag type: %r' % type(tag))
class AbstractDimensionPacket(Packet):
''' The abstract superclass of JoinGamePacket and RespawnPacket, containing
common definitions relating to their 'dimension' field.
'''
def field_string(self, field):
# pylint: disable=no-member
if self.context.protocol_version >= 748 and field == 'dimension':
return nbt_to_snbt(self.dimension)
elif self.context.protocol_version < 718 and field == 'dimension':
return Dimension.name_from_value(self.dimension)
return super(AbstractDimensionPacket, self).field_string(field)
class JoinGamePacket(AbstractDimensionPacket):
@staticmethod
def get_id(context):
return 0x24 if context.protocol_version >= 741 else \
0x25 if context.protocol_version >= 721 else \
0x26 if context.protocol_version >= 550 else \
0x25 if context.protocol_version >= 389 else \
0x24 if context.protocol_version >= 345 else \
0x23 if context.protocol_version >= 332 else \
0x24 if context.protocol_version >= 318 else \
0x23 if context.protocol_version >= 107 else \
0x01
packet_name = "join game"
get_definition = staticmethod(lambda context: [
{'entity_id': Integer},
{'is_hardcore': Boolean} if context.protocol_version >= 738 else {},
{'game_mode': UnsignedByte},
{'previous_game_mode': UnsignedByte}
if context.protocol_version >= 730 else {},
{'world_names': PrefixedArray(VarInt, String)}
if context.protocol_version >= 722 else {},
{'dimension_codec': NBT}
if context.protocol_version >= 718 else {},
{'dimension':
NBT if context.protocol_version >= 748 else
String if context.protocol_version >= 718 else
Integer if context.protocol_version >= 108 else
Byte},
{'world_name': String} if context.protocol_version >= 722 else {},
{'hashed_seed': Long} if context.protocol_version >= 552 else {},
{'difficulty': UnsignedByte} if context.protocol_version < 464 else {},
{'max_players':
VarInt if context.protocol_version >= 749 else UnsignedByte},
{'level_type': String} if context.protocol_version < 716 else {},
{'render_distance': VarInt} if context.protocol_version >= 468 else {},
{'reduced_debug_info': Boolean},
{'respawn_screen': Boolean} if context.protocol_version >= 571 else {},
{'is_debug': Boolean} if context.protocol_version >= 716 else {},
{'is_flat': Boolean} if context.protocol_version >= 716 else {},
])
# These aliases declare the Enum type corresponding to each field:
Difficulty = Difficulty
GameMode = GameMode
# Accesses the 'game_mode' field appropriately depending on the protocol.
# Can be set or deleted when 'context' is undefined.
@property
def game_mode(self):
if self.context.protocol_version >= 738:
return self._game_mode_738
else:
return self._game_mode_0
@game_mode.setter
def game_mode(self, value):
self._game_mode_738 = value
self._game_mode_0 = value
@game_mode.deleter
def game_mode(self):
del self._game_mode_738
del self._game_mode_0
# Accesses the 'is_hardcore' field, or its equivalent in older protocols.
# Can be set or deleted when 'context' is undefined.
@property
def is_hardcore(self):
if self.context.protocol_version >= 738:
return self._is_hardcore
else:
return bool(self._game_mode_0 & GameMode.HARDCORE)
@is_hardcore.setter
def is_hardcore(self, value):
self._is_hardcore = value
self._game_mode_0 = \
getattr(self, '_game_mode_0', 0) | GameMode.HARDCORE \
if value else \
getattr(self, '_game_mode_0', 0) & ~GameMode.HARDCORE
@is_hardcore.deleter
def is_hardcore(self):
if hasattr(self, '_is_hardcore'):
del self._is_hardcore
if hasattr(self, '_game_mode_0'):
self._game_mode_0 &= ~GameMode.HARDCORE
# Accesses the component of the 'game_mode' field without any hardcore bit,
# version-independently. Can be set or deleted when 'context' is undefined.
@property
def pure_game_mode(self):
if self.context.protocol_version >= 738:
return self._game_mode_738
else:
return self._game_mode_0 & ~GameMode.HARDCORE
@pure_game_mode.setter
def pure_game_mode(self, value):
self._game_mode_738 = value
self._game_mode_0 = \
value & ~GameMode.HARDCORE | \
getattr(self, '_game_mode_0', 0) & GameMode.HARDCORE
def field_string(self, field):
if field == 'dimension_codec':
# pylint: disable=no-member
return nbt_to_snbt(self.dimension_codec)
return super(JoinGamePacket, self).field_string(field)
class RespawnPacket(AbstractDimensionPacket):
@staticmethod
def get_id(context):
return 0x39 if context.protocol_version >= 741 else \
0x3A if context.protocol_version >= 721 else \
0x3B if context.protocol_version >= 550 else \
0x3A if context.protocol_version >= 471 else \
0x38 if context.protocol_version >= 461 else \
0x39 if context.protocol_version >= 451 else \
0x38 if context.protocol_version >= 389 else \
0x37 if context.protocol_version >= 352 else \
0x36 if context.protocol_version >= 345 else \
0x35 if context.protocol_version >= 336 else \
0x34 if context.protocol_version >= 332 else \
0x35 if context.protocol_version >= 318 else \
0x33 if context.protocol_version >= 70 else \
0x07
packet_name = 'respawn'
get_definition = staticmethod(lambda context: [
{'dimension':
NBT if context.protocol_version >= 748 else
String if context.protocol_version >= 718 else
Integer},
{'world_name': String} if context.protocol_version >= 719 else {},
{'difficulty': UnsignedByte} if context.protocol_version < 464 else {},
{'hashed_seed': Long} if context.protocol_version >= 552 else {},
{'game_mode': UnsignedByte},
{'previous_game_mode': UnsignedByte}
if context.protocol_version >= 730 else {},
{'level_type': String} if context.protocol_version < 716 else {},
{'is_debug': Boolean} if context.protocol_version >= 716 else {},
{'is_flat': Boolean} if context.protocol_version >= 716 else {},
{'copy_metadata': Boolean} if context.protocol_version >= 714 else {},
])
# These aliases declare the Enum type corresponding to each field:
Difficulty = Difficulty
GameMode = GameMode

View File

@ -8,7 +8,9 @@ from minecraft.networking.types import (
class MapPacket(Packet):
@staticmethod
def get_id(context):
return 0x27 if context.protocol_version >= 550 else \
return 0x25 if context.protocol_version >= 741 else \
0x26 if context.protocol_version >= 721 else \
0x27 if context.protocol_version >= 550 else \
0x26 if context.protocol_version >= 389 else \
0x25 if context.protocol_version >= 345 else \
0x24 if context.protocol_version >= 334 else \

View File

@ -9,7 +9,9 @@ from minecraft.networking.types import (
class PlayerListItemPacket(Packet):
@staticmethod
def get_id(context):
return 0x34 if context.protocol_version >= 550 else \
return 0x32 if context.protocol_version >= 741 else \
0x33 if context.protocol_version >= 721 else \
0x34 if context.protocol_version >= 550 else \
0x33 if context.protocol_version >= 471 else \
0x31 if context.protocol_version >= 451 else \
0x30 if context.protocol_version >= 389 else \

View File

@ -9,7 +9,9 @@ from minecraft.networking.types import (
class PlayerPositionAndLookPacket(Packet, BitFieldEnum):
@staticmethod
def get_id(context):
return 0x36 if context.protocol_version >= 550 else \
return 0x34 if context.protocol_version >= 741 else \
0x35 if context.protocol_version >= 721 else \
0x36 if context.protocol_version >= 550 else \
0x35 if context.protocol_version >= 471 else \
0x33 if context.protocol_version >= 451 else \
0x32 if context.protocol_version >= 389 else \

View File

@ -9,11 +9,11 @@ __all__ = 'SoundEffectPacket',
class SoundEffectPacket(Packet):
@staticmethod
def get_id(context):
return 0x52 if context.protocol_version >= 550 else \
return 0x51 if context.protocol_version >= 721 else \
0x52 if context.protocol_version >= 550 else \
0x51 if context.protocol_version >= 471 else \
0x4D if context.protocol_version >= 461 else \
0x4E if context.protocol_version >= 451 else \
0x4E if context.protocol_version >= 451 else \
0x4D if context.protocol_version >= 389 else \
0x4C if context.protocol_version >= 352 else \
0x4B if context.protocol_version >= 345 else \

View File

@ -49,6 +49,7 @@ class SpawnObjectPacket(Packet):
return getattr(cls, name)
class EntityType(Enum):
# XXX This has not been updated for >= v1.15
ACTIVATED_TNT = 50 if pv < 458 else 55 # PrimedTnt
AREA_EFFECT_CLOUD = 3 if pv < 458 else 0
ARMORSTAND = 78 if pv < 458 else 1

View File

@ -1,23 +1,28 @@
from .packet_buffer import PacketBuffer
from zlib import compress
from .packet_buffer import PacketBuffer
from minecraft.networking.types import (
VarInt, Enum
VarInt, Enum, overridable_property,
)
class Packet(object):
packet_name = "base"
id = None
definition = None
# To define the packet ID, either:
# 1. Define the attribute `id', of type int, in a subclass; or
# 2. Override `get_id' in a subclass and return the correct packet ID
# for the given ConnectionContext. This is necessary if the packet ID
# has changed across protocol versions, for example.
# has changed across protocol versions, for example; or
# 3. Define the attribute `id' in an instance of a class without either
# of the above.
@classmethod
def get_id(cls, context):
return cls.id
def get_id(cls, _context):
return getattr(cls, 'id')
@overridable_property
def id(self):
return None if self.context is None else self.get_id(self.context)
# To define the network data layout of a packet, either:
# 1. Define the attribute `definition', a list of fields, each of which
@ -29,37 +34,37 @@ class Packet(object):
# This may be necessary if the packet layout cannot be described as a
# simple list of fields.
@classmethod
def get_definition(cls, context):
return cls.definition
def get_definition(cls, _context):
return getattr(cls, 'definition')
@overridable_property
def definition(self):
return None if self.context is None else \
self.get_definition(self.context)
# In general, a packet instance must have its 'context' attribute set to an
# instance of 'ConnectionContext', for example to decide on version-
# dependent behaviour. This can either be given as an argument to this
# constructor (e.g. 'p = P(context=c)') or set later
# (e.g. 'p.context = c').
#
# While a packet has no 'context' set, all attributes should *writable*
# without errors, but some attributes may not be *readable*.
#
# When sending or receiving packets via 'Connection', it is generally not
# necessary to set the 'context', as this will be done automatically by
# 'Connection'.
def __init__(self, context=None, **kwargs):
self.context = context
self.set_values(**kwargs)
@property
def context(self):
return self._context
@context.setter
def context(self, _context):
self._context = _context
self._context_changed()
def _context_changed(self):
if self._context is not None:
self.id = self.get_id(self._context)
self.definition = self.get_definition(self._context)
else:
self.id = None
self.definition = None
def set_values(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
return self
def read(self, file_object):
for field in self.definition:
for field in self.definition: # pylint: disable=not-an-iterable
for var_name, data_type in field.items():
value = data_type.read_with_context(file_object, self.context)
setattr(self, var_name, value)
@ -101,7 +106,7 @@ class Packet(object):
def write_fields(self, packet_buffer):
# Write the fields comprising the body of the packet (excluding the
# length, packet ID, compression and encryption) into a PacketBuffer.
for field in self.definition:
for field in self.definition: # pylint: disable=not-an-iterable
for var_name, data_type in field.items():
data = getattr(self, var_name)
data_type.send_with_context(data, packet_buffer, self.context)
@ -122,6 +127,7 @@ class Packet(object):
""" An iterable of the names of the packet's fields, or None. """
if self.definition is None:
return None
# pylint: disable=not-an-iterable
return (field for defn in self.definition for field in defn)
def field_string(self, field):

View File

@ -5,7 +5,7 @@ from minecraft.networking.packets import (
from minecraft.networking.types import (
Double, Float, Boolean, VarInt, String, Byte, Position, Enum,
RelativeHand, BlockFace, Vector, Direction, PositionAndLook,
multi_attribute_alias
multi_attribute_alias,
)
from .client_settings_packet import ClientSettingsPacket
@ -37,7 +37,8 @@ def get_packets(context):
class KeepAlivePacket(AbstractKeepAlivePacket):
@staticmethod
def get_id(context):
return 0x0F if context.protocol_version >= 471 else \
return 0x10 if context.protocol_version >= 712 else \
0x0F if context.protocol_version >= 471 else \
0x10 if context.protocol_version >= 464 else \
0x0E if context.protocol_version >= 389 else \
0x0C if context.protocol_version >= 386 else \
@ -78,7 +79,8 @@ class ChatPacket(Packet):
class PositionAndLookPacket(Packet):
@staticmethod
def get_id(context):
return 0x12 if context.protocol_version >= 471 else \
return 0x13 if context.protocol_version >= 712 else \
0x12 if context.protocol_version >= 471 else \
0x13 if context.protocol_version >= 464 else \
0x11 if context.protocol_version >= 389 else \
0x0F if context.protocol_version >= 386 else \
@ -124,7 +126,9 @@ class TeleportConfirmPacket(Packet):
class AnimationPacket(Packet):
@staticmethod
def get_id(context):
return 0x2A if context.protocol_version >= 468 else \
return 0x2C if context.protocol_version >= 738 else \
0x2B if context.protocol_version >= 712 else \
0x2A if context.protocol_version >= 468 else \
0x29 if context.protocol_version >= 464 else \
0x27 if context.protocol_version >= 389 else \
0x25 if context.protocol_version >= 386 else \
@ -197,7 +201,9 @@ class PlayerBlockPlacementPacket(Packet):
@staticmethod
def get_id(context):
return 0x2C if context.protocol_version >= 468 else \
return 0x2E if context.protocol_version >= 738 else \
0x2D if context.protocol_version >= 712 else \
0x2C if context.protocol_version >= 468 else \
0x2B if context.protocol_version >= 464 else \
0x29 if context.protocol_version >= 389 else \
0x27 if context.protocol_version >= 386 else \
@ -234,7 +240,9 @@ class PlayerBlockPlacementPacket(Packet):
class UseItemPacket(Packet):
@staticmethod
def get_id(context):
return 0x2D if context.protocol_version >= 468 else \
return 0x2F if context.protocol_version >= 738 else \
0x2E if context.protocol_version >= 712 else \
0x2D if context.protocol_version >= 468 else \
0x2C if context.protocol_version >= 464 else \
0x2A if context.protocol_version >= 389 else \
0x28 if context.protocol_version >= 386 else \

View File

@ -2,32 +2,35 @@
Each type has a method which is used to read and write it.
These definitions and methods are used by the packet definitions
"""
from __future__ import division
import struct
import uuid
import io
from .utility import Vector
import pynbt
from .utility import Vector, class_and_instancemethod
__all__ = (
'Type', 'Boolean', 'UnsignedByte', 'Byte', 'Short', 'UnsignedShort',
'Integer', 'FixedPointInteger', 'Angle', 'VarInt', 'Long',
'Integer', 'FixedPointInteger', 'Angle', 'VarInt', 'VarLong', 'Long',
'UnsignedLong', 'Float', 'Double', 'ShortPrefixedByteArray',
'VarIntPrefixedByteArray', 'TrailingByteArray', 'String', 'UUID',
'Position',
'Position', 'NBT', 'PrefixedArray',
)
class Type(object):
# pylint: disable=no-self-argument
__slots__ = ()
@classmethod
def read_with_context(cls, file_object, _context):
return cls.read(file_object)
@class_and_instancemethod
def read_with_context(cls_or_self, file_object, _context):
return cls_or_self.read(file_object)
@classmethod
def send_with_context(cls, value, socket, _context):
return cls.send(value, socket)
@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):
@ -131,12 +134,13 @@ class Angle(Type):
class VarInt(Type):
@staticmethod
def read(file_object):
max_bytes = 5
@classmethod
def read(cls, file_object):
number = 0
# Limit of 5 bytes, otherwise its possible to cause
# a DOS attack by sending VarInts that just keep
# going
# Limit of 'cls.max_bytes' bytes, otherwise its possible to cause
# a DOS attack by sending VarInts that just keep going
bytes_encountered = 0
while True:
byte = file_object.read(1)
@ -149,7 +153,7 @@ class VarInt(Type):
break
bytes_encountered += 1
if bytes_encountered > 5:
if bytes_encountered > cls.max_bytes:
raise ValueError("Tried to read too long of a VarInt")
return number
@ -172,6 +176,10 @@ class VarInt(Type):
raise ValueError("Integer too large")
class VarLong(VarInt):
max_bytes = 10
# Maps (maximum integer value -> size of VarInt in bytes)
VARINT_SIZE_TABLE = {
2 ** 7: 1,
@ -324,3 +332,48 @@ class Position(Type, Vector):
if context.protocol_version >= 443 else
(x & 0x3FFFFFF) << 38 | (y & 0xFFF) << 26 | (z & 0x3FFFFFF))
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)

View File

@ -99,13 +99,22 @@ class Dimension(Enum):
OVERWORLD = 0
END = 1
from_identifier_dict = {
'minecraft:the_nether': NETHER,
'minecraft:overworld': OVERWORLD,
'minecraft:the_end': END,
}
to_identifier_dict = {e: i for (i, e) in from_identifier_dict.items()}
# Designation of a player's gamemode.
class GameMode(Enum):
class GameMode(BitFieldEnum):
SURVIVAL = 0
CREATIVE = 1
ADVENTURE = 2
SPECTATOR = 3
HARDCORE = 8 # Only used prior to protocol 738.
# Currently designates an entity's feet or eyes.

View File

@ -1,7 +1,7 @@
"""Minecraft data types that are used by packets, but don't have a specific
network representation.
"""
from __future__ import division
import types
from collections import namedtuple
from itertools import chain
@ -9,7 +9,8 @@ from itertools import chain
__all__ = (
'Vector', 'MutableRecord', 'Direction', 'PositionAndLook', 'descriptor',
'attribute_alias', 'multi_attribute_alias',
'overridable_descriptor', 'overridable_property', 'attribute_alias',
'multi_attribute_alias',
)
@ -144,23 +145,58 @@ def multi_attribute_alias(container, *arg_names, **kwd_names):
return alias
class descriptor(object):
"""Behaves identically to the builtin 'property' function of Python,
except that the getter, setter and deleter functions given by the
user are used as the raw __get__, __set__ and __delete__ functions
as defined in Python's descriptor protocol.
class overridable_descriptor:
"""As 'descriptor' (defined below), except that only a getter can be
defined, and the resulting descriptor has no '__set__' or '__delete__'
methods defined; hence, attributes defined via this class can be
overridden by attributes of instances of the class in which it occurs.
"""
__slots__ = '_fget', '_fset', '_fdel'
__slots__ = '_fget',
def __init__(self, fget=None, fset=None, fdel=None):
def __init__(self, fget=None):
self._fget = fget if fget is not None else self._default_get
self._fset = fset if fset is not None else self._default_set
self._fdel = fdel if fdel is not None else self._default_del
def getter(self, fget):
self._fget = fget
return self
@staticmethod
def _default_get(instance, owner):
raise AttributeError('unreadable attribute')
def __get__(self, instance, owner):
return self._fget(self, instance, owner)
class overridable_property(overridable_descriptor):
"""As the builtin 'property' decorator of Python, except that only
a getter is defined and the resulting descriptor is a non-data
descriptor, overridable by attributes of instances of the class
in which the property occurs. See also 'overridable_descriptor' above.
"""
def __get__(self, instance, _owner):
return self._fget(instance)
class descriptor(overridable_descriptor):
"""Behaves identically to the builtin 'property' decorator of Python,
except that the getter, setter and deleter functions given by the
user are used as the raw __get__, __set__ and __delete__ functions
as defined in Python's descriptor protocol.
Since an instance of this class always havs '__set__' and '__delete__'
defined, it is a "data descriptor", so its binding behaviour cannot be
overridden in instances of the class in which it occurs. See
https://docs.python.org/3/reference/datamodel.html#descriptor-invocation
for more information. See also 'overridable_descriptor' above.
"""
__slots__ = '_fset', '_fdel'
def __init__(self, fget=None, fset=None, fdel=None):
super(descriptor, self).__init__(fget=fget)
self._fset = fset if fset is not None else self._default_set
self._fdel = fdel if fdel is not None else self._default_del
def setter(self, fset):
self._fset = fset
return self
@ -169,10 +205,6 @@ class descriptor(object):
self._fdel = fdel
return self
@staticmethod
def _default_get(instance, owner):
raise AttributeError('unreadable attribute')
@staticmethod
def _default_set(instance, value):
raise AttributeError("can't set attribute")
@ -181,9 +213,6 @@ class descriptor(object):
def _default_del(instance):
raise AttributeError("can't delete attribute")
def __get__(self, instance, owner):
return self._fget(self, instance, owner)
def __set__(self, instance, value):
return self._fset(self, instance, value)
@ -191,6 +220,26 @@ class descriptor(object):
return self._fdel(self, instance)
class class_and_instancemethod:
""" A decorator for functions defined in a class namespace which are to be
accessed as both class and instance methods: retrieving the method from
a class will return a bound class method (like the built-in
'classmethod' decorator), but retrieving the method from an instance
will return a bound instance method (as if the function were not
decorated). Therefore, the first argument of the decorated function may
be either a class or an instance, depending on how it was called.
"""
__slots__ = '_func',
def __init__(self, func):
self._func = func
def __get__(self, inst, owner=None):
bind_to = owner if inst is None else inst
return types.MethodType(self._func, bind_to)
Direction = namedtuple('Direction', ('yaw', 'pitch'))

View File

@ -1 +1,3 @@
# Package dependencies are stored in setup.py.
# For more information, see <https://github.com/ammaraskar/pyCraft/pull/156>.
-e .

View File

@ -24,7 +24,7 @@ setup(name="pyCraft",
author=", ".join(MAIN_AUTHORS),
install_requires=["cryptography>=1.5",
"requests",
"future",
"pynbt",
],
packages=["minecraft",
"minecraft.networking",

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python
from __future__ import print_function
import getpass
import sys
import re
@ -11,7 +9,6 @@ from minecraft import authentication
from minecraft.exceptions import YggdrasilError
from minecraft.networking.connection import Connection
from minecraft.networking.packets import Packet, clientbound, serverbound
from minecraft.compat import input
def get_options():
@ -35,6 +32,10 @@ def get_options():
action="store_true",
help="print sent and received packets to standard error")
parser.add_option("-v", "--dump-unknown-packets", dest="dump_unknown",
action="store_true",
help="include unknown packets in --dump-packets output")
(options, args) = parser.parse_args()
if not options.username:
@ -81,9 +82,12 @@ def main():
def print_incoming(packet):
if type(packet) is Packet:
# This is a direct instance of the base Packet type, meaning
# that it is a packet of unknown type, so we do not print it.
return
print('--> %s' % packet, file=sys.stderr)
# that it is a packet of unknown type, so we do not print it
# unless explicitly requested by the user.
if options.dump_unknown:
print('--> [unknown packet] %s' % packet, file=sys.stderr)
else:
print('--> %s' % packet, file=sys.stderr)
def print_outgoing(packet):
print('<-- %s' % packet, file=sys.stderr)

View File

@ -1,7 +0,0 @@
import platform
from distutils.version import StrictVersion
if StrictVersion(platform.python_version()) < StrictVersion("3.3.0"):
import mock # noqa
else:
from unittest import mock # noqa

View File

@ -1,4 +1,4 @@
from __future__ import print_function
import pynbt
from minecraft import SUPPORTED_MINECRAFT_VERSIONS
from minecraft.networking import connection
@ -11,7 +11,6 @@ from minecraft.networking.encryption import (
)
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from future.utils import raise_
from numbers import Integral
import unittest
@ -101,10 +100,46 @@ class FakeClientHandler(object):
def handle_play_start(self):
# Called upon entering the play state.
self.write_packet(clientbound.play.JoinGamePacket(
entity_id=0, game_mode=0, dimension=0, hashed_seed=12345,
difficulty=2, max_players=1, level_type='default',
reduced_debug_info=False, render_distance=9, respawn_screen=False))
packet = clientbound.play.JoinGamePacket(
entity_id=0, is_hardcore=False, game_mode=0, previous_game_mode=0,
world_names=['minecraft:overworld'],
world_name='minecraft:overworld',
hashed_seed=12345, difficulty=2, max_players=1,
level_type='default', reduced_debug_info=False, render_distance=9,
respawn_screen=False, is_debug=False, is_flat=False)
if self.server.context.protocol_version >= 748:
packet.dimension = pynbt.TAG_Compound({
'natural': pynbt.TAG_Byte(1),
'effects': pynbt.TAG_String('minecraft:overworld'),
}, '')
packet.dimension_codec = pynbt.TAG_Compound({
'minecraft:dimension_type': pynbt.TAG_Compound({
'type': pynbt.TAG_String('minecraft:dimension_type'),
'value': pynbt.TAG_List(pynbt.TAG_Compound, [
pynbt.TAG_Compound(packet.dimension),
]),
}),
'minecraft:worldgen/biome': pynbt.TAG_Compound({
'type': pynbt.TAG_String('minecraft:worldgen/biome'),
'value': pynbt.TAG_List(pynbt.TAG_Compound, [
pynbt.TAG_Compound({
'id': pynbt.TAG_Int(1),
'name': pynbt.TAG_String('minecraft:plains'),
}),
pynbt.TAG_Compound({
'id': pynbt.TAG_Int(2),
'name': pynbt.TAG_String('minecraft:desert'),
}),
]),
}),
}, '')
elif self.server.context.protocol_version >= 718:
packet.dimension = 'minecraft:overworld'
else:
packet.dimension = types.Dimension.OVERWORLD
self.write_packet(packet)
def handle_play_packet(self, packet):
# Called upon each packet received after handle_play_start() returns.
@ -181,7 +216,8 @@ class FakeClientHandler(object):
try:
self.handle_connection()
packet = self.read_packet()
assert isinstance(packet, serverbound.handshake.HandShakePacket)
assert isinstance(packet, serverbound.handshake.HandShakePacket), \
type(packet)
self.handle_handshake(packet)
if packet.next_state == 1:
self._run_status()
@ -576,7 +612,8 @@ class _FakeServerTest(unittest.TestCase):
logging.error(**error)
self.fail('Multiple errors: see logging output.')
elif errors and 'exc_info' in errors[0]:
raise_(*errors[0]['exc_info'])
exc_value, exc_tb = errors[0]['exc_info'][1:]
raise exc_value.with_traceback(exc_tb)
elif errors:
self.fail(errors[0]['msg'])

View File

@ -4,11 +4,11 @@ from minecraft.authentication import _make_request
from minecraft.authentication import _raise_from_response
from minecraft.exceptions import YggdrasilError
from unittest import mock
import unittest
import requests
import json
import unittest
import os
from .compat import mock
FAKE_DATA = {
"id_": "85e2c12b9eab4a7dabf61babc11354c2",

View File

@ -101,3 +101,56 @@ class ClassMemberAliasesTest(unittest.TestCase):
packet = clientbound.play.BlockChangePacket(blockId=bi, blockMeta=bm)
self.assertEqual((packet.blockId, packet.blockMeta), (bi, bm))
self.assertEqual(packet.blockStateId, packet.block_state_id)
def test_join_game_packet(self):
GameMode = types.GameMode
context = ConnectionContext()
for pure_game_mode in (GameMode.SURVIVAL, GameMode.CREATIVE,
GameMode.ADVENTURE, GameMode.SPECTATOR):
for is_hardcore in (False, True):
context.protocol_version = 70
game_mode = \
pure_game_mode | GameMode.HARDCORE \
if is_hardcore else pure_game_mode
packet = clientbound.play.JoinGamePacket()
packet.game_mode = game_mode
packet.context = context
self.assertEqual(packet.pure_game_mode, pure_game_mode)
self.assertEqual(packet.is_hardcore, is_hardcore)
del packet.context
del packet.is_hardcore
packet.context = context
self.assertEqual(packet.game_mode, packet.pure_game_mode)
del packet.context
del packet.game_mode
packet.context = context
self.assertFalse(hasattr(packet, 'is_hardcore'))
packet = clientbound.play.JoinGamePacket()
packet.pure_game_mode = game_mode
packet.is_hardcore = is_hardcore
packet.context = context
self.assertEqual(packet.game_mode, game_mode)
context.protocol_version = 738
game_mode = pure_game_mode | GameMode.HARDCORE
packet = clientbound.play.JoinGamePacket()
packet.game_mode = game_mode
packet.is_hardcore = is_hardcore
packet.context = context
self.assertEqual(packet.game_mode, game_mode)
self.assertEqual(packet.pure_game_mode, game_mode)
self.assertEqual(packet.is_hardcore, is_hardcore)
del packet.context
packet.is_hardcore = is_hardcore
packet.context = context
self.assertEqual(packet.game_mode, game_mode)
self.assertEqual(packet.pure_game_mode, game_mode)
with self.assertRaises(AttributeError):
del packet.pure_game_mode

View File

@ -5,7 +5,6 @@ from minecraft.networking.connection import Connection
from minecraft.exceptions import (
VersionMismatch, LoginDisconnect, InvalidState, IgnorePacket
)
from minecraft.compat import unicode
from . import fake_server
@ -92,7 +91,7 @@ class DefaultStatusTest(ConnectTest):
def setUp(self):
class FakeStdOut(io.BytesIO):
def write(self, data):
if isinstance(data, unicode):
if isinstance(data, str):
data = data.encode('utf8')
super(FakeStdOut, self).write(data)
sys.stdout, self.old_stdout = FakeStdOut(), sys.stdout

View File

@ -186,11 +186,9 @@ class TestReadWritePackets(unittest.TestCase):
packet = clientbound.play.CombatEventPacket(
event=clientbound.play.CombatEventPacket.EndCombatEvent(
duration=415, entity_id=91063502))
self.assertEqual(
str(packet),
'CombatEventPacket(event=EndCombatEvent('
'duration=415, entity_id=91063502))'
)
self.assertEqual(str(packet),
'CombatEventPacket(event=EndCombatEvent('
'duration=415, entity_id=91063502))')
self._test_read_write_packet(packet)
packet = clientbound.play.CombatEventPacket(
@ -205,26 +203,32 @@ class TestReadWritePackets(unittest.TestCase):
def test_multi_block_change_packet(self):
Record = clientbound.play.MultiBlockChangePacket.Record
packet = clientbound.play.MultiBlockChangePacket(
chunk_x=167, chunk_z=15, records=[
Record(x=1, y=2, z=3, blockId=56, blockMeta=13),
Record(position=Vector(1, 2, 3), block_state_id=909),
Record(position=(1, 2, 3), blockStateId=909)])
self.assertEqual(packet.records[0].blockId, 56)
self.assertEqual(packet.records[0].blockMeta, 13)
self.assertEqual(packet.records[0].blockStateId, 909)
self.assertEqual(packet.records[0].position, Vector(1, 2, 3))
self.assertEqual(packet.chunk_pos, (packet.chunk_x, packet.chunk_z))
self.assertEqual(
str(packet),
'MultiBlockChangePacket(chunk_x=167, chunk_z=15, records=['
'Record(x=1, y=2, z=3, block_state_id=909), '
'Record(x=1, y=2, z=3, block_state_id=909), '
'Record(x=1, y=2, z=3, block_state_id=909)])'
)
for protocol_version in TEST_VERSIONS:
context = ConnectionContext()
context.protocol_version = protocol_version
packet = clientbound.play.MultiBlockChangePacket(context)
self._test_read_write_packet(packet)
if protocol_version >= 741:
packet.chunk_section_pos = Vector(167, 17, 33)
packet.invert_trust_edges = False
else:
packet.chunk_x, packet.chunk_z = 167, 17
self.assertEqual(packet.chunk_pos, (167, 17))
packet.records = [
Record(x=1, y=2, z=3, blockId=56, blockMeta=13),
Record(position=Vector(1, 2, 3), block_state_id=909),
Record(position=(1, 2, 3), blockStateId=909),
]
for i in range(3):
self.assertEqual(packet.records[i].blockId, 56)
self.assertEqual(packet.records[i].blockMeta, 13)
self.assertEqual(packet.records[i].blockStateId, 909)
self.assertEqual(packet.records[i].position, Vector(1, 2, 3))
self._test_read_write_packet(packet, context)
def test_spawn_object_packet(self):
for protocol_version in TEST_VERSIONS:

View File

@ -4,7 +4,7 @@
# and then run "tox" from this directory.
[tox]
envlist = py27, py35, py36, py37, py38, pypy, flake8, pylint-errors, pylint-full, verify-manifest
envlist = py35, py36, py37, py38, pypy, flake8, pylint-errors, pylint-full, verify-manifest
[testenv]
commands = nosetests --with-timer
@ -27,11 +27,6 @@ commands =
deps =
coveralls
[testenv:py27]
deps =
{[testenv]deps}
mock
[testenv:py38]
setenv =
PYCRAFT_RUN_INTERNET_TESTS=1