mirror of
https://github.com/ammaraskar/pyCraft.git
synced 2025-01-22 15:41:29 +01:00
Fix some pylint concerns
This commit is contained in:
parent
f7ed4da0d0
commit
dcb2845ac4
@ -5,19 +5,17 @@ import threading
|
||||
import socket
|
||||
import time
|
||||
import select
|
||||
import authentication
|
||||
|
||||
from packets import *
|
||||
import packets
|
||||
from types import VarInt
|
||||
|
||||
PROTOCOL_VERSION = 47
|
||||
from minecraft import PROTOCOL_VERSION
|
||||
|
||||
|
||||
class ConnectionOptions(object):
|
||||
# TODO: allow these options to be overriden from a constructor below
|
||||
address = None
|
||||
port = None
|
||||
use_encryption = True
|
||||
compression_threshold = -1
|
||||
compression_enabled = False
|
||||
|
||||
@ -40,8 +38,11 @@ class Connection(object):
|
||||
spawned = False
|
||||
|
||||
def __init__(self, address, port, auth_token):
|
||||
"""Sets up an instance of this object to be able to connect to a minecraft server.
|
||||
The connect method needs to be called in order to actually begin the connection
|
||||
"""Sets up an instance of this object to be able to connect to a
|
||||
minecraft server.
|
||||
|
||||
The connect method needs to be called in order to actually begin
|
||||
the connection
|
||||
|
||||
:param address: address of the server to connect to
|
||||
:param port(int): port of the server to connect to
|
||||
@ -58,10 +59,12 @@ class Connection(object):
|
||||
|
||||
def write_packet(self, packet, force=False):
|
||||
"""Writes a packet to the server.
|
||||
If force is set to true, the method attempts to acquire the write lock and write the packet
|
||||
out immediately, and as such may block.
|
||||
If force is false then the packet will be added to the end of the packet writing queue
|
||||
to be sent 'as soon as possible'
|
||||
|
||||
If force is set to true, the method attempts to acquire the write lock
|
||||
and write the packet out immediately, and as such may block.
|
||||
|
||||
If force is false then the packet will be added to the end of the
|
||||
packet writing queue to be sent 'as soon as possible'
|
||||
|
||||
:param packet: The :class:`network.packets.Packet` to write
|
||||
:param force(bool): Specifies if the packet write should be immediate
|
||||
@ -84,14 +87,17 @@ class Connection(object):
|
||||
:param method: The method which will be called back with the packet
|
||||
:param args: The packets to listen for
|
||||
"""
|
||||
self.packet_listeners.append(PacketListener(method, *args))
|
||||
self.packet_listeners.append(packets.PacketListener(method, *args))
|
||||
|
||||
def _pop_packet(self):
|
||||
# Pops the topmost packet off the outgoing queue and writes it out through the socket
|
||||
# Pops the topmost packet off the outgoing queue and writes it out
|
||||
# through the socket
|
||||
#
|
||||
# Mostly an internal convenience function, caller should make sure they have the
|
||||
# write lock acquired to avoid issues caused by asynchronous access to the socket.
|
||||
# This should be the only method that removes elements from the outbound queue
|
||||
# Mostly an internal convenience function, caller should make sure
|
||||
# they have the write lock acquired to avoid issues caused by
|
||||
# asynchronous access to the socket.
|
||||
# This should be the only method that removes elements from the
|
||||
# outbound queue
|
||||
if len(self._outgoing_packet_queue) == 0:
|
||||
return False
|
||||
else:
|
||||
@ -108,7 +114,7 @@ class Connection(object):
|
||||
self._start_network_thread()
|
||||
self.reactor = StatusReactor(self)
|
||||
|
||||
request_packet = RequestPacket()
|
||||
request_packet = packets.RequestPacket()
|
||||
self.write_packet(request_packet)
|
||||
|
||||
def connect(self):
|
||||
@ -119,21 +125,23 @@ class Connection(object):
|
||||
|
||||
self.reactor = LoginReactor(self)
|
||||
self._start_network_thread()
|
||||
login_start_packet = LoginStartPacket()
|
||||
login_start_packet = packets.LoginStartPacket()
|
||||
login_start_packet.name = self.auth_token.profile.name
|
||||
self.write_packet(login_start_packet)
|
||||
|
||||
def _connect(self):
|
||||
# Connect a socket to the server and create a file object from the socket
|
||||
# The file object is used to read any and all data from the socket since it's "guaranteed"
|
||||
# to read the number of bytes specified, the socket itself will mostly be
|
||||
# used to write data upstream to the server
|
||||
# Connect a socket to the server and create a file object from the
|
||||
# socket.
|
||||
# The file object is used to read any and all data from the socket
|
||||
# since it's "guaranteed" to read the number of bytes specified,
|
||||
# the socket itself will mostly be used to write data upstream to
|
||||
# the server.
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.connect((self.options.address, self.options.port))
|
||||
self.file_object = self.socket.makefile()
|
||||
|
||||
def _handshake(self, next_state=2):
|
||||
handshake = HandShakePacket()
|
||||
handshake = packets.HandShakePacket()
|
||||
handshake.protocol_version = PROTOCOL_VERSION
|
||||
handshake.server_address = self.options.address
|
||||
handshake.server_port = self.options.port
|
||||
@ -155,7 +163,8 @@ class NetworkingThread(threading.Thread):
|
||||
while True:
|
||||
if self.interrupt:
|
||||
break
|
||||
# Attempt to write out as many as 300 packets as possible every 0.05 seconds (20 ticks per second)
|
||||
# Attempt to write out as many as 300 packets as possible every
|
||||
# 0.05 seconds (20 ticks per second)
|
||||
num_packets = 0
|
||||
self.connection._write_lock.acquire()
|
||||
while self.connection._pop_packet():
|
||||
@ -166,7 +175,8 @@ class NetworkingThread(threading.Thread):
|
||||
|
||||
# Read and react to as many as 50 packets
|
||||
num_packets = 0
|
||||
packet = self.connection.reactor.read_packet(self.connection.file_object)
|
||||
packet = self.connection.reactor.read_packet(
|
||||
self.connection.file_object)
|
||||
while packet:
|
||||
num_packets += 1
|
||||
|
||||
@ -176,7 +186,8 @@ class NetworkingThread(threading.Thread):
|
||||
|
||||
if num_packets >= 50:
|
||||
break
|
||||
packet = self.connection.reactor.read_packet(self.connection.file_object)
|
||||
packet = self.connection.reactor.read_packet(
|
||||
self.connection.file_object)
|
||||
|
||||
time.sleep(0.05)
|
||||
|
||||
@ -194,23 +205,26 @@ class PacketReactor(object):
|
||||
self.connection = connection
|
||||
|
||||
def read_packet(self, stream):
|
||||
ready_to_read, _, __ = select.select([self.connection.socket], [], [], self.TIME_OUT)
|
||||
ready_to_read, _, __ = select.select([self.connection.socket], [], [],
|
||||
self.TIME_OUT)
|
||||
|
||||
if self.connection.socket in ready_to_read:
|
||||
length = VarInt.read_socket(self.connection.socket)
|
||||
|
||||
packet_data = PacketBuffer()
|
||||
packet_data = packets.PacketBuffer()
|
||||
packet_data.send(stream.read(length))
|
||||
# Ensure we read all the packet
|
||||
while len(packet_data.get_writable()) < length:
|
||||
packet_data.send(stream.read(length - len(packet_data.get_writable())))
|
||||
packet_data.send(
|
||||
stream.read(length - len(packet_data.get_writable())))
|
||||
packet_data.reset_cursor()
|
||||
|
||||
if self.connection.options.compression_enabled:
|
||||
compressed_size = VarInt.read(packet_data)
|
||||
|
||||
if compressed_size > 0:
|
||||
decompressed_packet = decompress(packet_data.read(compressed_size))
|
||||
decompressed_packet = decompress(
|
||||
packet_data.read(compressed_size))
|
||||
packet_data.reset()
|
||||
packet_data.send(decompressed_packet)
|
||||
packet_data.reset_cursor()
|
||||
@ -224,7 +238,7 @@ class PacketReactor(object):
|
||||
packet.read(packet_data)
|
||||
return packet
|
||||
else:
|
||||
return Packet()
|
||||
return packets.Packet()
|
||||
else:
|
||||
return None
|
||||
|
||||
@ -233,39 +247,39 @@ class PacketReactor(object):
|
||||
|
||||
|
||||
class LoginReactor(PacketReactor):
|
||||
clientbound_packets = state_login_clientbound
|
||||
clientbound_packets = packets.STATE_LOGIN_CLIENTBOUND
|
||||
|
||||
def react(self, packet):
|
||||
# TODO: Add some way to bypass encryption? (connection.options.use_encryption) Not sure if it's still possible.
|
||||
if packet.packet_name == "encryption request":
|
||||
import encryption
|
||||
|
||||
secret = encryption.generate_shared_secret()
|
||||
encrypted_token, encrypted_secret = encryption.encrypt_token_and_secret(packet.public_key,
|
||||
packet.verify_token, secret)
|
||||
token, encrypted_secret = encryption.encrypt_token_and_secret(
|
||||
packet.public_key, packet.verify_token, secret)
|
||||
|
||||
# A server id of '-' means the server is in offline mode
|
||||
if packet.server_id != '-':
|
||||
server_id = encryption.generate_verification_hash(packet.server_id, secret, packet.public_key)
|
||||
|
||||
self.connection.auth_token.join(server_id)
|
||||
|
||||
encryption_response = EncryptionResponsePacket()
|
||||
encryption_response.shared_secret = encrypted_secret
|
||||
encryption_response.verify_token = encrypted_token
|
||||
server_id = encryption.generate_verification_hash(
|
||||
packet.server_id, secret, packet.public_key)
|
||||
|
||||
# Forced because we don't want to send this encrypted which it will be
|
||||
# if we put it in the queue as we'd have wrapped the socket and file object by then
|
||||
self.connection.auth_token.join(server_id)
|
||||
|
||||
encryption_response = packets.EncryptionResponsePacket()
|
||||
encryption_response.shared_secret = encrypted_secret
|
||||
encryption_response.verify_token = token
|
||||
|
||||
# Forced because we'll have encrypted the connection by the time
|
||||
# it reaches the outgoing queue
|
||||
self.connection.write_packet(encryption_response, force=True)
|
||||
|
||||
# Enable the encryption
|
||||
cipher = encryption.create_AES_cipher(secret)
|
||||
encryptor = cipher.encryptor()
|
||||
decryptor = cipher.decryptor()
|
||||
self.connection.socket = encryption.EncryptedSocketWrapper(self.connection.socket,
|
||||
encryptor, decryptor)
|
||||
self.connection.file_object = encryption.EncryptedFileObjectWrapper(self.connection.file_object,
|
||||
decryptor)
|
||||
self.connection.socket = encryption.EncryptedSocketWrapper(
|
||||
self.connection.socket, encryptor, decryptor)
|
||||
self.connection.file_object = encryption.EncryptedFileObjectWrapper(
|
||||
self.connection.file_object, decryptor)
|
||||
|
||||
if packet.packet_name == "disconnect":
|
||||
print(packet.json_data) # TODO: handle propagating this back
|
||||
@ -279,7 +293,7 @@ class LoginReactor(PacketReactor):
|
||||
|
||||
|
||||
class PlayingReactor(PacketReactor):
|
||||
clientbound_packets = state_playing_clientbound
|
||||
clientbound_packets = packets.STATE_PLAYING_CLIENTBOUND
|
||||
|
||||
def react(self, packet):
|
||||
if packet.packet_name == "set compression":
|
||||
@ -287,12 +301,12 @@ class PlayingReactor(PacketReactor):
|
||||
self.connection.options.compression_enabled = True
|
||||
|
||||
if packet.packet_name == "keep alive":
|
||||
keep_alive_packet = KeepAlivePacket()
|
||||
keep_alive_packet = packets.KeepAlivePacket()
|
||||
keep_alive_packet.keep_alive_id = packet.keep_alive_id
|
||||
self.connection.write_packet(keep_alive_packet)
|
||||
|
||||
if packet.packet_name == "player position and look":
|
||||
position_response = PositionAndLookPacket()
|
||||
position_response = packets.PositionAndLookPacket()
|
||||
position_response.x = packet.x
|
||||
position_response.feet_y = packet.y
|
||||
position_response.z = packet.z
|
||||
@ -308,15 +322,15 @@ class PlayingReactor(PacketReactor):
|
||||
|
||||
|
||||
class StatusReactor(PacketReactor):
|
||||
clientbound_packets = state_status_clientbound
|
||||
clientbound_packets = packets.STATE_STATUS_CLIENTBOUND
|
||||
|
||||
def react(self, packet):
|
||||
if packet.id == ResponsePacket.id:
|
||||
if packet.id == packets.ResponsePacket.id:
|
||||
import json
|
||||
|
||||
print json.loads(packet.json_response)
|
||||
|
||||
ping_packet = PingPacket()
|
||||
ping_packet = packets.PingPacket()
|
||||
ping_packet.time = int(time.time())
|
||||
self.connection.write_packet(ping_packet)
|
||||
|
||||
|
@ -12,12 +12,14 @@ def generate_shared_secret():
|
||||
|
||||
|
||||
def create_AES_cipher(shared_secret):
|
||||
cipher = Cipher(algorithms.AES(shared_secret), modes.CFB8(shared_secret), backend=default_backend())
|
||||
cipher = Cipher(algorithms.AES(shared_secret), modes.CFB8(shared_secret),
|
||||
backend=default_backend())
|
||||
return cipher
|
||||
|
||||
|
||||
def encrypt_token_and_secret(pubkey, verification_token, shared_secret):
|
||||
"""Encrypts the verification token and shared secret with the server's public key
|
||||
"""Encrypts the verification token and shared secret
|
||||
with the server's public key.
|
||||
|
||||
:param pubkey: The RSA public key provided by the server
|
||||
:param verification_token: The verification token provided by the server
|
||||
@ -45,8 +47,8 @@ def generate_verification_hash(server_id, shared_secret, public_key):
|
||||
|
||||
|
||||
def minecraft_sha1_hash_digest(sha1_hash):
|
||||
# Minecraft first parses the sha1 bytes as a signed number and then spits outs
|
||||
# its hex representation
|
||||
# Minecraft first parses the sha1 bytes as a signed number and then
|
||||
# spits outs its hex representation
|
||||
number_representation = _number_from_bytes(sha1_hash.digest(), signed=True)
|
||||
return format(number_representation, 'x')
|
||||
|
||||
@ -82,4 +84,4 @@ class EncryptedSocketWrapper(object):
|
||||
self.actual_socket.send(self.encryptor.update(data))
|
||||
|
||||
def fileno(self):
|
||||
return self.actual_socket.fileno()
|
||||
return self.actual_socket.fileno()
|
||||
|
@ -102,10 +102,10 @@ class HandShakePacket(Packet):
|
||||
{'next_state': VarInt}]
|
||||
|
||||
|
||||
state_handshake_clientbound = {
|
||||
STATE_HANDSHAKE_CLIENTBOUND = {
|
||||
|
||||
}
|
||||
state_handshake_serverbound = {
|
||||
STATE_HANDSHAKE_SERVERBOUND = {
|
||||
0x00: HandShakePacket
|
||||
}
|
||||
|
||||
@ -125,7 +125,7 @@ class PingPacket(Packet):
|
||||
definition = [
|
||||
{'time': Long}]
|
||||
|
||||
state_status_clientbound = {
|
||||
STATE_STATUS_CLIENTBOUND = {
|
||||
0x00: ResponsePacket,
|
||||
0x01: PingPacket
|
||||
}
|
||||
@ -143,7 +143,7 @@ class PingPacket(Packet):
|
||||
definition = [
|
||||
{'time': Long}]
|
||||
|
||||
state_status_serverbound = {
|
||||
STATE_STATUS_SERVERBOUND = {
|
||||
0x00: RequestPacket,
|
||||
0x01: PingPacket
|
||||
}
|
||||
@ -181,7 +181,7 @@ class SetCompressionPacket(Packet):
|
||||
definition = [
|
||||
{'threshold': VarInt}]
|
||||
|
||||
state_login_clientbound = {
|
||||
STATE_LOGIN_CLIENTBOUND = {
|
||||
0x00: DisconnectPacket,
|
||||
0x01: EncryptionRequestPacket,
|
||||
0x02: LoginSuccessPacket,
|
||||
@ -203,7 +203,7 @@ class EncryptionResponsePacket(Packet):
|
||||
{'shared_secret': VarIntPrefixedByteArray},
|
||||
{'verify_token': VarIntPrefixedByteArray}]
|
||||
|
||||
state_login_serverbound = {
|
||||
STATE_LOGIN_SERVERBOUND = {
|
||||
0x00: LoginStartPacket,
|
||||
0x01: EncryptionResponsePacket
|
||||
}
|
||||
@ -267,7 +267,7 @@ class SetCompressionPacketPlayState(Packet):
|
||||
{'threshold': VarInt}]
|
||||
|
||||
|
||||
state_playing_clientbound = {
|
||||
STATE_PLAYING_CLIENTBOUND = {
|
||||
0x00: KeepAlivePacket,
|
||||
0x01: JoinGamePacket,
|
||||
0x02: ChatMessagePacket,
|
||||
@ -295,7 +295,7 @@ class PositionAndLookPacket(Packet):
|
||||
{'pitch': Float},
|
||||
{'on_ground': Boolean}]
|
||||
|
||||
state_playing_serverbound = {
|
||||
STATE_PLAYING_SERVERBOUND = {
|
||||
0x00: KeepAlivePacket,
|
||||
0x01: ChatPacket,
|
||||
0x06: PositionAndLookPacket
|
||||
|
@ -4,14 +4,13 @@ from minecraft.networking.encryption import minecraft_sha1_hash_digest
|
||||
|
||||
|
||||
class Hashing(unittest.TestCase):
|
||||
|
||||
test_data = {'Notch': '4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48',
|
||||
'jeb_': '-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1',
|
||||
'simon': '88e16a1019277b15d58faf0541e11910eb756f6'}
|
||||
'jeb_': '-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1',
|
||||
'simon': '88e16a1019277b15d58faf0541e11910eb756f6'}
|
||||
|
||||
def test_hashing(self):
|
||||
for input_value in self.test_data.iterkeys():
|
||||
sha1_hash = hashlib.sha1()
|
||||
sha1_hash.update(input_value)
|
||||
self.assertEquals(minecraft_sha1_hash_digest(sha1_hash), self.test_data[input_value])
|
||||
|
||||
self.assertEquals(minecraft_sha1_hash_digest(sha1_hash),
|
||||
self.test_data[input_value])
|
||||
|
Loading…
Reference in New Issue
Block a user