mirror of
https://github.com/ammaraskar/pyCraft.git
synced 2024-11-16 07:15:24 +01:00
Prepare to support multiple protocol versions.
This commit is contained in:
parent
9affeee041
commit
3927400178
@ -2,6 +2,10 @@
|
||||
A modern, Python3-compatible, well-documented library for communicating
|
||||
with a MineCraft server.
|
||||
"""
|
||||
__version__ = "0.0.1"
|
||||
MINECRAFT_VERSION = "1.8.3"
|
||||
PROTOCOL_VERSION = 47
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
SUPPORTED_PROTOCOL_VERSIONS = (
|
||||
47, # Minecraft 1.8 - 1.8.9
|
||||
)
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
from collections import deque
|
||||
from collections import namedtuple
|
||||
from threading import Lock
|
||||
from zlib import decompress
|
||||
import threading
|
||||
@ -10,8 +11,7 @@ import sys
|
||||
from .types import VarInt
|
||||
from . import packets
|
||||
from . import encryption
|
||||
from .. import PROTOCOL_VERSION
|
||||
|
||||
from .. import SUPPORTED_PROTOCOL_VERSIONS
|
||||
|
||||
class _ConnectionOptions(object):
|
||||
def __init__(self,
|
||||
@ -25,13 +25,22 @@ class _ConnectionOptions(object):
|
||||
self.compression_threshold = compression_threshold
|
||||
self.compression_enabled = compression_enabled
|
||||
|
||||
ConnectionContext = namedtuple('ConnectionContext', (
|
||||
'protocol_version'
|
||||
))
|
||||
|
||||
class Connection(object):
|
||||
"""This class represents a connection to a minecraft
|
||||
server, it handles everything from connecting, sending packets to
|
||||
handling default network behaviour
|
||||
"""
|
||||
def __init__(self, address, port, auth_token):
|
||||
def __init__(
|
||||
self,
|
||||
address,
|
||||
port,
|
||||
auth_token,
|
||||
protocol_version=max(SUPPORTED_PROTOCOL_VERSIONS)
|
||||
):
|
||||
"""Sets up an instance of this object to be able to connect to a
|
||||
minecraft server.
|
||||
|
||||
@ -56,6 +65,10 @@ class Connection(object):
|
||||
self.options.port = port
|
||||
self.auth_token = auth_token
|
||||
|
||||
self.context = ConnectionContext(
|
||||
protocol_version = protocol_version
|
||||
)
|
||||
|
||||
# The reactor handles all the default responses to packets,
|
||||
# it should be changed per networking state
|
||||
self.reactor = PacketReactor(self)
|
||||
@ -76,6 +89,7 @@ class Connection(object):
|
||||
:param packet: The :class:`network.packets.Packet` to write
|
||||
:param force(bool): Specifies if the packet write should be immediate
|
||||
"""
|
||||
packet.context = self.context
|
||||
if force:
|
||||
self._write_lock.acquire()
|
||||
if self.options.compression_enabled:
|
||||
@ -149,7 +163,7 @@ class Connection(object):
|
||||
|
||||
def _handshake(self, next_state=2):
|
||||
handshake = packets.HandShakePacket()
|
||||
handshake.protocol_version = PROTOCOL_VERSION
|
||||
handshake.protocol_version = self.context.protocol_version
|
||||
handshake.server_address = self.options.address
|
||||
handshake.server_port = self.options.port
|
||||
handshake.next_state = next_state
|
||||
@ -210,11 +224,16 @@ class PacketReactor(object):
|
||||
Reads and reacts to packets
|
||||
"""
|
||||
state_name = None
|
||||
clientbound_packets = None
|
||||
TIME_OUT = 0
|
||||
|
||||
get_clientbound_packets = staticmethod(lambda context: set())
|
||||
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
context = self.connection.context
|
||||
self.clientbound_packets = {
|
||||
packet.get_id(context): packet
|
||||
for packet in self.__class__.get_clientbound_packets(context)}
|
||||
|
||||
def read_packet(self, stream):
|
||||
ready_to_read = select.select([self.connection.socket], [], [],
|
||||
@ -247,10 +266,11 @@ class PacketReactor(object):
|
||||
# otherwise just skip it
|
||||
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()
|
||||
return packets.Packet(context=self.connection.context)
|
||||
else:
|
||||
return None
|
||||
|
||||
@ -259,7 +279,7 @@ class PacketReactor(object):
|
||||
|
||||
|
||||
class LoginReactor(PacketReactor):
|
||||
clientbound_packets = packets.STATE_LOGIN_CLIENTBOUND
|
||||
get_clientbound_packets = staticmethod(packets.state_login_clientbound)
|
||||
|
||||
def react(self, packet):
|
||||
if packet.packet_name == "encryption request":
|
||||
@ -307,7 +327,7 @@ class LoginReactor(PacketReactor):
|
||||
|
||||
|
||||
class PlayingReactor(PacketReactor):
|
||||
clientbound_packets = packets.STATE_PLAYING_CLIENTBOUND
|
||||
get_clientbound_packets = staticmethod(packets.state_playing_clientbound)
|
||||
|
||||
def react(self, packet):
|
||||
if packet.packet_name == "set compression":
|
||||
@ -337,10 +357,10 @@ class PlayingReactor(PacketReactor):
|
||||
'''
|
||||
|
||||
class StatusReactor(PacketReactor):
|
||||
clientbound_packets = packets.STATE_STATUS_CLIENTBOUND
|
||||
get_clientbound_packets = staticmethod(packets.state_status_clientbound)
|
||||
|
||||
def react(self, packet):
|
||||
if packet.id == packets.ResponsePacket.id:
|
||||
if packet.id == packets.ResponsePacket.get_id(self.connection.context):
|
||||
import json
|
||||
|
||||
print(json.loads(packet.json_response))
|
||||
|
@ -50,11 +50,34 @@ class PacketListener(object):
|
||||
|
||||
class Packet(object):
|
||||
packet_name = "base"
|
||||
id = -0x01
|
||||
definition = []
|
||||
id = None
|
||||
definition = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
# 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.
|
||||
@classmethod
|
||||
def get_id(cls, context):
|
||||
return cls.id
|
||||
|
||||
# To define the network data layout of a packet, either:
|
||||
# 1. Define the attribute `definition', a list of fields, each of which
|
||||
# is a dict mapping attribute names to data types; or
|
||||
# 2. Override `get_definition' in a subclass and return the correct
|
||||
# definition for the given ConnectionContext. This may be necessary
|
||||
# if the layout has changed across protocol versions, for example; or
|
||||
# 3. Override the methods `read' and/or `write' in a subclass. This may be
|
||||
# necessary if the packet layout cannot be described as a list of fields.
|
||||
@classmethod
|
||||
def get_definition(cls, context):
|
||||
return cls.definition
|
||||
|
||||
def __init__(self, context=None, **kwargs):
|
||||
self.context = context
|
||||
self.id = self.get_id(context)
|
||||
self.definition = self.get_definition(context)
|
||||
|
||||
def set_values(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
@ -112,14 +135,15 @@ class HandShakePacket(Packet):
|
||||
{'next_state': VarInt}]
|
||||
|
||||
|
||||
STATE_HANDSHAKE_CLIENTBOUND = {
|
||||
def state_handshake_clientbound(context):
|
||||
return {
|
||||
|
||||
}
|
||||
STATE_HANDSHAKE_SERVERBOUND = {
|
||||
0x00: HandShakePacket
|
||||
def state_handshake_serverbound(context):
|
||||
return {
|
||||
HandShakePacket
|
||||
}
|
||||
|
||||
|
||||
# Status State
|
||||
# ==============
|
||||
class ResponsePacket(Packet):
|
||||
@ -136,9 +160,10 @@ class PingPacketResponse(Packet):
|
||||
{'time': Long}]
|
||||
|
||||
|
||||
STATE_STATUS_CLIENTBOUND = {
|
||||
0x00: ResponsePacket,
|
||||
0x01: PingPacketResponse
|
||||
def state_status_clientbound(context):
|
||||
return {
|
||||
ResponsePacket,
|
||||
PingPacketResponse
|
||||
}
|
||||
|
||||
|
||||
@ -155,9 +180,10 @@ class PingPacket(Packet):
|
||||
{'time': Long}]
|
||||
|
||||
|
||||
STATE_STATUS_SERVERBOUND = {
|
||||
0x00: RequestPacket,
|
||||
0x01: PingPacket
|
||||
def state_status_serverbound(context):
|
||||
return {
|
||||
RequestPacket,
|
||||
PingPacket
|
||||
}
|
||||
|
||||
|
||||
@ -194,11 +220,12 @@ class SetCompressionPacket(Packet):
|
||||
{'threshold': VarInt}]
|
||||
|
||||
|
||||
STATE_LOGIN_CLIENTBOUND = {
|
||||
0x00: DisconnectPacket,
|
||||
0x01: EncryptionRequestPacket,
|
||||
0x02: LoginSuccessPacket,
|
||||
0x03: SetCompressionPacket
|
||||
def state_login_clientbound(context):
|
||||
return {
|
||||
DisconnectPacket,
|
||||
EncryptionRequestPacket,
|
||||
LoginSuccessPacket,
|
||||
SetCompressionPacket
|
||||
}
|
||||
|
||||
|
||||
@ -217,9 +244,10 @@ class EncryptionResponsePacket(Packet):
|
||||
{'verify_token': VarIntPrefixedByteArray}]
|
||||
|
||||
|
||||
STATE_LOGIN_SERVERBOUND = {
|
||||
0x00: LoginStartPacket,
|
||||
0x01: EncryptionResponsePacket
|
||||
def state_login_serverbound(context):
|
||||
return {
|
||||
LoginStartPacket,
|
||||
EncryptionResponsePacket
|
||||
}
|
||||
|
||||
|
||||
@ -496,15 +524,16 @@ class MapPacket(Packet):
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
STATE_PLAYING_CLIENTBOUND = {
|
||||
0x00: KeepAlivePacket,
|
||||
0x01: JoinGamePacket,
|
||||
0x02: ChatMessagePacket,
|
||||
0x08: PlayerPositionAndLookPacket,
|
||||
0x34: MapPacket,
|
||||
0x38: PlayerListItemPacket,
|
||||
0x40: DisconnectPacketPlayState,
|
||||
0x46: SetCompressionPacketPlayState
|
||||
def state_playing_clientbound(context):
|
||||
return {
|
||||
KeepAlivePacket,
|
||||
JoinGamePacket,
|
||||
ChatMessagePacket,
|
||||
PlayerPositionAndLookPacket,
|
||||
MapPacket,
|
||||
PlayerListItemPacket,
|
||||
DisconnectPacketPlayState,
|
||||
SetCompressionPacketPlayState
|
||||
}
|
||||
|
||||
|
||||
@ -526,8 +555,10 @@ class PositionAndLookPacket(Packet):
|
||||
{'pitch': Float},
|
||||
{'on_ground': Boolean}]
|
||||
|
||||
STATE_PLAYING_SERVERBOUND = {
|
||||
0x00: KeepAlivePacket,
|
||||
0x01: ChatPacket,
|
||||
0x06: PositionAndLookPacket
|
||||
|
||||
def state_playing_serverbound(context):
|
||||
return {
|
||||
KeepAlivePacket,
|
||||
ChatPacket,
|
||||
PositionAndLookPacket
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user