Source code for quarry.net.client

from twisted.internet import reactor, protocol, defer
from twisted.python import failure

from quarry.net.protocol import Factory, Protocol, ProtocolError, \
    protocol_modes_inv
from quarry.net import auth, crypto


[docs]class ClientProtocol(Protocol): """This class represents a connection to a server""" recv_direction = "downstream" send_direction = "upstream" # Convenience functions --------------------------------------------------- def switch_protocol_mode(self, mode): self.check_protocol_mode_switch(mode) if mode in ("status", "login"): # Send handshake addr = self.transport.connector.getDestination() self.send_packet( "handshake", self.buff_type.pack_varint(self.protocol_version) + self.buff_type.pack_string(addr.host) + self.buff_type.pack('H', addr.port) + self.buff_type.pack_varint( protocol_modes_inv[self.factory.protocol_mode_next])) # Switch buff type self.buff_type = self.factory.get_buff_type(self.protocol_version) self.protocol_mode = mode if mode == "status": # Send status request self.send_packet("status_request") elif mode == "login": # Send login start self.send_packet("login_start", self.buff_type.pack_string( self.factory.profile.display_name)) # Callbacks --------------------------------------------------------------- @defer.inlineCallbacks def connection_made(self): """Called when the connection is established""" super(ClientProtocol, self).connection_made() # Determine protocol version if self.factory.protocol_mode_next == "status": pass elif self.factory.force_protocol_version is not None: self.protocol_version = self.factory.force_protocol_version else: factory = PingClientFactory() factory.connect(self.remote_addr.host, self.remote_addr.port) self.protocol_version = yield factory.detected_protocol_version self.switch_protocol_mode(self.factory.protocol_mode_next) def auth_ok(self, data): """ Called if the Mojang session server responds to our query. Note that this method does not indicate that the server accepted our session; in this case :meth:`player_joined` is called. """ # Send encryption response p_shared_secret = crypto.encrypt_secret( self.public_key, self.shared_secret) p_verify_token = crypto.encrypt_secret( self.public_key, self.verify_token) # 1.7.x if self.protocol_version <= 5: pack_array = lambda d: self.buff_type.pack('h', len(d)) + d # 1.8.x else: pack_array = lambda d: self.buff_type.pack_varint( len(d), max_bits=16) + d self.send_packet( "login_encryption_response", pack_array(p_shared_secret) + pack_array(p_verify_token)) # Enable encryption self.cipher.enable(self.shared_secret) self.logger.debug("Encryption enabled") def player_joined(self): """ Called when we join the game. If the server is in online mode, this means the server accepted our session. """ Protocol.player_joined(self) self.logger.info("Joined the game.") def player_left(self): """Called when we leave the game.""" Protocol.player_left(self) self.logger.info("Left the game.") def status_response(self, data): """ If we're connecting in "status" mode, this is called when the server sends us information about itself. """ self.close() # Packet handlers --------------------------------------------------------- def packet_status_response(self, buff): p_data = buff.unpack_json() self.status_response(p_data) def packet_login_plugin_request(self, buff): p_message_id = buff.unpack_varint() p_channel = buff.unpack_string() p_payload = buff.read() self.send_packet( "login_plugin_response", self.buff_type.pack_varint(p_message_id), self.buff_type.pack('?', False)) def packet_login_disconnect(self, buff): p_data = buff.unpack_chat() self.logger.warn("Kicked: %s" % p_data) self.close() def packet_login_encryption_request(self, buff): p_server_id = buff.unpack_string() # 1.7.x if self.protocol_version <= 5: unpack_array = lambda b: b.read(b.unpack('h')) # 1.8.x else: unpack_array = lambda b: b.read(b.unpack_varint(max_bits=16)) p_public_key = unpack_array(buff) p_verify_token = unpack_array(buff) if not self.factory.profile.online: raise ProtocolError("Can't log into online-mode server while using" " offline profile") self.shared_secret = crypto.make_shared_secret() self.public_key = crypto.import_public_key(p_public_key) self.verify_token = p_verify_token # make digest digest = crypto.make_digest( p_server_id.encode('ascii'), self.shared_secret, p_public_key) # do auth deferred = self.factory.profile.join(digest) deferred.addCallbacks(self.auth_ok, self.auth_failed) def packet_login_success(self, buff): p_uuid = buff.unpack_string() p_display_name = buff.unpack_string() self.switch_protocol_mode("play") self.player_joined() def packet_login_set_compression(self, buff): self.set_compression(buff.unpack_varint()) def packet_set_compression(self, buff): self.set_compression(buff.unpack_varint()) packet_disconnect = packet_login_disconnect
[docs]class SpawningClientProtocol(ClientProtocol): spawned = False def __init__(self, factory, remote_addr): # x, y, z, yaw, pitch self.pos_look = [0, 0, 0, 0, 0] super(SpawningClientProtocol, self).__init__(factory, remote_addr) # Send a 'player' packet every tick def update_player_inc(self): self.send_packet("player", self.buff_type.pack('?', True)) # Sent a 'player position and look' packet every 20 ticks def update_player_full(self): self.send_packet( "player_position_and_look", self.buff_type.pack( 'dddff?', self.pos_look[0], self.pos_look[1], self.pos_look[2], self.pos_look[3], self.pos_look[4], True)) def packet_player_position_and_look(self, buff): p_pos_look = buff.unpack('dddff') # 1.7.x if self.protocol_version <= 5: p_on_ground = buff.unpack('?') self.pos_look = p_pos_look # 1.8.x else: p_flags = buff.unpack('B') for i in range(5): if p_flags & (1 << i): self.pos_look[i] += p_pos_look[i] else: self.pos_look[i] = p_pos_look[i] # 1.9.x if self.protocol_version > 47: teleport_id = buff.unpack_varint() # Send Player Position And Look # 1.7.x if self.protocol_version <= 5: self.send_packet("player_position_and_look", self.buff_type.pack( 'ddddff?', self.pos_look[0], self.pos_look[1] - 1.62, self.pos_look[1], self.pos_look[2], self.pos_look[3], self.pos_look[4], True)) # 1.8.x elif self.protocol_version <= 47: self.send_packet("player_position_and_look", self.buff_type.pack( 'dddff?', self.pos_look[0], self.pos_look[1], self.pos_look[2], self.pos_look[3], self.pos_look[4], True)) # 1.9.x else: self.send_packet("teleport_confirm", self.buff_type.pack_varint( teleport_id)) if not self.spawned: self.spawn() def spawn(self): self.ticker.add_loop(1, self.update_player_inc) self.ticker.add_loop(20, self.update_player_full) self.spawned = True def packet_keep_alive(self, buff): self.send_packet('keep_alive', buff.read())
[docs]class ClientFactory(Factory, protocol.ClientFactory): protocol = ClientProtocol protocol_mode_next = "login"
[docs] def __init__(self, profile=None): if profile is None: profile = auth.OfflineProfile() self.profile = profile
[docs] def connect(self, host, port=25565): reactor.connectTCP(host, port, self, self.connection_timeout)
class PingClientProtocol(ClientProtocol): def status_response(self, data): self.close() detected_version = int(data["version"]["protocol"]) if detected_version in self.factory.minecraft_versions: self.factory.detected_protocol_version.callback(detected_version) else: self.factory.detected_protocol_version.errback( failure.Failure(ProtocolError( "Unsupported protocol version: %d" % detected_version))) class PingClientFactory(ClientFactory): protocol = PingClientProtocol protocol_mode_next = "status" def __init__(self, profile=None): super(PingClientFactory, self).__init__(profile) self.detected_protocol_version = defer.Deferred()