def handle_unknown_peer(self, msg_params, from_addr, msg_id): """ Handle the UNKNOWN_PEER message """ self.known_peers.discard(from_addr) yatelog.info( 'YATESock', 'Peer %s:%s does not know us, perhaps we timed out?' % from_addr)
def handle_login_success(self,buff): self.avatar_uuid = buff.unpack_string() self.avatar_name = buff.unpack_string() self.sock.switch_mode("play") yatelog.info('minecraft','Connected to minecraft server %s:%s with display name "%s" and avatar UUID %s' % (self.server_addr[0],self.server_addr[1],self.avatar_name,self.avatar_uuid)) self.sock.send_client_settings(buffer.Buffer.pack_string('en_GB'), # locale buffer.Buffer.pack_byte(1), # view distance buffer.Buffer.pack_varint(0), # chat is enabled buffer.Buffer.pack('?',False), # disable chat colors buffer.Buffer.pack_byte(0xFF), # enable all the displayed skin parts buffer.Buffer.pack_varint(1)) # right handed, not sure if this matters
def __init__(self, bind_ip='127.0.0.1', bind_port=0, handlers={}, enable_null_handle=True): """ handlers is a dict mapping message type integers to functions that take the params (msg_params,msg_id,from_addr,sock) enable_null_handle enables a default "null handler" that does nothing with unhandled message types except logging them to debug """ self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.bind((bind_ip, bind_port)) yatelog.info('YATESock', 'Bound %s:%s' % self.sock.getsockname()) yatelog.info('YATESock', 'Setting up handlers and queues') self.pool = eventlet.GreenPool(1000) self.in_queues = { } # packets coming in from remote peer go here after parsing, each message type has an independent queue so we can do QoS-type stuff self.out_queues = {} # packets going out to remote peer go here self.parse_q = eventlet.queue.LightQueue( 0) # to keep up performance, packets go here before parsing self.handlers = { MSGTYPE_CONNECT: self. handle_connect, # a couple of standard message handlers, override by passing in new handlers MSGTYPE_UNKNOWN_PEER: self.handle_unknown_peer, MSGTYPE_CONNECT_ACK: self.handle_connect_ack, MSGTYPE_KEEPALIVE: self.handle_keepalive, MSGTYPE_KEEPALIVE_ACK: self.handle_keepalive_ack } self.handlers.update(handlers) self.enable_null_handle = enable_null_handle self.active = True for x in xrange(10): self.pool.spawn_n(self.parser_thread) for k, v in msgtype_str.items(): self.in_queues[k] = eventlet.queue.LightQueue(0) self.out_queues[k] = eventlet.queue.LightQueue(0) setattr(self, 'send_%s' % v[8:].lower(), YATESockSendMethod(k, self)) # black magic for x in xrange(2): self.pool.spawn_n(self.msg_sender_thread, k) for x in xrange(2): self.pool.spawn_n(self.msg_reader_thread, k) if enable_null_handle: if not self.handlers.has_key(k): self.handlers[k] = self.null_handler self.known_peers = set( ) # if this is a server, this set contains the list of clients, if it's a client this contains only 1 member - the server self.last_pack = { } # store the timestamp of the last packet from a particular peer so we can do timeouts self.pool.spawn_n(self.recv_thread) self.pool.spawn_n( self.timeout_thread ) # timeout peers all in a central location, giving plenty of time for them to send packets and not timeout
def timeout_thread(self): """ kill peers that have timed out """ while self.active: eventlet.greenthread.sleep(YATE_KEEPALIVE_TIMEOUT) cur_time = time.time() peer_list = self.known_peers.copy() # thread safety bitches for peer in peer_list: if not self.last_pack.has_key(peer): yatelog.warn( 'YATESock', 'Peer %s:%s never actually sent us a single packet after connecting' % peer) self.known_peers.discard(peer) else: if cur_time - self.last_pack[peer] > YATE_KEEPALIVE_TIMEOUT: yatelog.info('YATESock', 'Peer %s:%s has timed out, bye' % peer) self.known_peers.discard(peer)
def switch_mode(self, new_mode=protocol_modes['login']): """ Use this to switch protocol mode after a connection is established new_mode is the integer describing the new mode to switch to (default is login) status is not supported because YATE has no use for it """ if not (type(new_mode) is int): new_mode = protocol_modes[new_mode] yatelog.info( 'MCSock', 'Switching protocol modes: %s to %s' % (protocol_modes[self.protocol_mode], protocol_modes[new_mode])) oldmode = self.protocol_mode self.protocol_mode = new_mode if new_mode == protocol_modes['login']: self.send_handshake( buffer.Buffer.pack_varint(self.protocol_version), buffer.Buffer.pack_string(self.endpoint[0]), buffer.Buffer.pack('H', self.endpoint[1]), buffer.Buffer.pack_varint(protocol_modes['login']), protomode=oldmode) if self.protocol_mode == protocol_modes['login']: self.send_login_start(buffer.Buffer.pack_string(self.display_name))
def __init__(self,username=None,password=None,server='127.0.0.1:25565'): super(MinecraftDriver,self).__init__(username=username,password=password,server=server) self.username = username self.password = password self.avatar_uuid = None self.avatar_eid = 0 self.avatar_name = username # not always the same, just usually server_ip,server_port = server.split(':') self.server_addr = (server_ip,int(server_port)) yatelog.info('minecraft','Minecraft driver starting up') pack_handlers = {'login_success': self.handle_login_success, 'join_game': self.handle_join_game, 'player_position_and_look':self.handle_player_position_and_look, 'chunk_data': self.handle_chunk_data} self.sock = mcsock.MCSocket(self.server_addr,handlers=pack_handlers,display_name=username) self.sock.switch_mode(mcsock.protocol_modes['login']) self.tick_delay = (1.0/20.0) self.last_tick = time.time() - self.tick_delay # make sure that we tick after connecting self.last_full_update = time.time() - 1.0 # make sure we run a full update after connecting self.av_pos = None # stores the avatar position, in minecraft format also known as (x,z,y) to sane humans self.av_pitch = None self.av_yaw = None self.on_ground = True self.world = smpmap.Dimension(smpmap.DIMENSION_OVERWORLD) self.sock.blocking_handlers = False yatelog.info('minecraft','Awaiting download of avatar position and terrain data') while self.av_pos is None: eventlet.greenthread.sleep(self.tick_delay) self.minecraft_client_tick() yatelog.info('minecraft','Got avatar position, awaiting chunks') while self.world.get_block(self.av_pos[0],self.av_pos[2],self.av_pos[1]) is None: self.minecraft_client_tick() yatelog.debug('minecraft','Waiting for chunk %s' % str(self.get_av_chunk()) ) yatelog.info('minecraft','Got terrain data, ready to rock')
def handle_join_game(self,buff): self.avatar_eid = buff.unpack_int() yatelog.info('minecraft','We are entity ID %s' % self.avatar_eid) self.sock.send_plugin_message(buffer.Buffer.pack_string('MC|Brand'), buffer.Buffer.pack_string('YATE minecraft driver'))
def connect_to(self, addr): """ Connect to the specified remote peer - this pretty much only really makes sense for clients """ yatelog.info('YATESock', 'Connecting to peer at %s:%s' % addr) msg_id = self.send_connect(to_addr=addr) self.handle_connect(tuple(), addr, msg_id)
def null_handler(self, msg_params, from_addr, msg_id): """ null handler - just dumps the message to log """ yatelog.info( 'YATESock', 'Null handler dump: message ID %s from %s:%s: %s' % (msg_id, from_addr[0], from_addr[1], str(msg_params)))