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