def _handle_packet(self, packet): """ Find and call a handler for that packet. It is possible that we do not know the opcode, which is not a problem. If it is known, it should be legal (in a valid state) and we need to enforce a handler to it, even if it's just NopHandler. If I don't feel like adding the simplest handler for an opcode, it probably shouldn't be in the OpCode enum in the first place. If an opcode is not valid, we do not throw an error anymore because there are some cases where misc opcodes can be received just after changing states. """ opcode, packet_data = self._parse_packet(packet) if opcode is None: return if (self.state not in self.UNMANAGED_STATES and opcode not in self.UNMANAGED_OPS and not self.opcode_is_legal(opcode)): LOG.debug("{}: received illegal opcode {} in state {}".format( type(self).__name__, opcode.name, self.state.name)) return handler_class = self.OP_HANDLERS.get(opcode, self.DEFAULT_HANDLER) self._call_handler(handler_class, packet_data)
def create_char(char_values): """ Try to create a new character and add it to the database. Return 0 on success, 1 on unspecified failure, 2 on name already used, 3 if the race and class combination isn't supported. The arg char_values is a tuple containing the Character data in the order they're defined, from name to features. This last value has to be a tuple with CharacterFeatures fields values. This should check of other things like account char limit etc. """ consts = _CharacterCreator._get_constants(char_values) if consts is None: return 3 if CharacterManager.does_char_with_name_exist(char_values["name"]): return 2 char_data = _CharacterCreator._try_create_char(char_values, consts) if char_data is None: return 1 _CharacterCreator._add_default_skills(char_data, consts) _CharacterCreator._add_default_spells(char_data, consts) LOG.debug("Character " + char_data.name + " created.") return 0
def _accept_clients(self): """ Accept incoming clients connections until manual interruption. """ try: while not self.shutdown_flag.is_set(): self._try_accept_client() except KeyboardInterrupt: LOG.info("KeyboardInterrupt received, stop accepting clients.")
def delete_session(account): """ Delete the session assiociated with that account. """ try: session = AccountSession.get(AccountSession.account == account) session.delete_instance() except AccountSession.DoesNotExist: LOG.warning("Tried to delete an non-existing session.")
def _accept_clients(self): """ Regularly try to access client while looking for interrupts. """ try: while True: self._try_accept_client() except KeyboardInterrupt: LOG.info("KeyboardInterrupt received, stop accepting clients.")
def get_account(account_name): """ Return the account from the database if it exists, or None. """ try: return Account.get(Account.name == account_name) except Account.DoesNotExist: LOG.warning("No account with that name: " + account_name) return None
def process(self): guid = self.PACKET_BIN.unpack(self.packet)[0] character_data = self._get_checked_character(guid) if character_data is None: LOG.warning( "Account {} tried to illegally use character {}".format( self.conn.account.name, guid)) return self.conn.MAIN_ERROR_STATE, None # Now that we have the character data, spawn a new player object. self.conn.set_player(character_data) # Finally, send the packets necessary to let the client get in world. # Only the tutorial flags and update object packets are really necessary # to let the client show the world. self.conn.send_packet(self._get_verify_login_packet()) self.conn.send_packet(self._get_account_data_md5_packet()) self.conn.send_packet(self._get_tutorial_flags_packet()) self.conn.send_packet(self._get_update_object_packet()) # self.conn.send_packet(self._get_initial_spells_packet()) self._get_near_objects() self._notify_near_players() return WorldConnectionState.IN_WORLD, None
def _recv_packet(self): try: packet = self.world_packet_receiver.get_next_packet() return packet except ConnectionResetError: LOG.info("Lost connection with " + self.account.name + ".") return None
def _add_required_fields(self, player): for required_field in PLAYER_SPAWN_FIELDS: value = player.get(required_field) if value is None: LOG.error("A required field for player spawning is not set.") LOG.error(str(required_field)) continue self.add_field(required_field, value)
def _actions_after_main_loop(self): LOG.debug("WorldConnection: session ended.") if self.account and self.session_cipher: AccountSessionManager.delete_session(self.account) if self.player: self.unset_player() with self.server.world_connections_lock: self.server.world_connections.remove(self)
def remove_player(self, guid): """ Remove the player from the object list and save its data. """ player = self.get_player(guid) if player is None: LOG.warning("Tried to remove a non-existing player.") return self._remove_object(guid) self.save_player(player)
def _get_response_packet(self, manager_code): response_code = { 0: CharDeleteResponseCode.SUCCESS, 1: CharDeleteResponseCode.FAILED }.get(manager_code, 1) LOG.debug("Character deletion status: " + str(response_code.name)) response_data = self.RESPONSE_BIN.pack(response_code.value) return WorldPacket(OpCode.SMSG_CHAR_DELETE, response_data)
def _try_handle_packet(self, packet): try: self._handle_packet(packet) except Exception as exc: LOG.error("{}: uncaught exception in packet handler:".format( type(self).__name__)) LOG.error(str(exc)) traceback.print_tb(exc.__traceback__) self.state = self.MAIN_ERROR_STATE
def _handle_client(self, connection, address): """ Start the threaded WorldConnection and add it to the local list. """ address_string = str(address[0]) + ":" + str(address[1]) LOG.info("Accepting client connection from " + address_string) world_connection = WorldConnection(self, connection) with self.world_connections_lock: self.world_connections.append(world_connection) simple_thread(world_connection.handle_connection)
def start(self): LOG.info("Starting world server " + self.realm.name) self._listen_clients() simple_thread(self._handle_login_server_connection) self._accept_clients() self.shutdown_flag.set() self._stop_listen_clients() LOG.info("World server stopped.")
def process(self): if "worldport_ack_pending" in self.conn.shared_data: LOG.debug("Received expected " + str(OpCode.MSG_MOVE_WORLDPORT_ACK)) del self.conn.shared_data["worldport_ack_pending"] return None, None else: LOG.error("Received unexpected " + str(OpCode.MSG_MOVE_WORLDPORT_ACK)) return self.conn.MAIN_ERROR_STATE, None
def _slice_packet_opcode(self): """ Cut the packet opcode from content. """ opcode_bytes = self.content[:4] opcode_value = int.from_bytes(opcode_bytes, "little") self.content = self.content[4:] try: self.opcode = OpCode(opcode_value) except ValueError: LOG.warning("Unknown opcode {:X}".format(opcode_value))
def _get_response_packet(self, manager_code): response_code = { 0: CharCreateResponseCode.SUCCESS, 1: CharCreateResponseCode.FAILED, 2: CharCreateResponseCode.NAME_IN_USE, 3: CharCreateResponseCode.ERROR }.get(manager_code, 1) LOG.debug("Character creation status: " + str(response_code.name)) response_data = self.RESPONSE_BIN.pack(response_code.value) return WorldPacket(OpCode.SMSG_CHAR_CREATE, response_data)
def _try_create_char(char_values, consts): char_data = None with DB.atomic() as transaction: try: char_data = _CharacterCreator._create_char(char_values, consts) except PeeweeException as exc: LOG.error("An error occured while creating character:") LOG.error(str(exc)) transaction.rollback() return None return char_data
def _open_login_server_socket(self): """ Open the login server socket, or set it to None if it couldn't connect properly. """ self.login_server_socket = socket.socket() login_server_address = (CONFIG["login"]["realm_conn_hostname"], int(CONFIG["login"]["realm_conn_port"])) try: self.login_server_socket.connect(login_server_address) except ConnectionError as exc: LOG.error("Couldn't join login server! " + str(exc)) self.login_server_socket = None
def process(self): self._parse_packet(self.packet) self._generate_local_proof() if self.client_proof == self.local_proof: LOG.debug("Reconnection: correct proof") response = self._get_success_response() return LoginConnectionState.RECON_PROOF, response else: LOG.warning("Reconnection: wrong proof!") response = self._get_failure_response() return LoginConnectionState.CLOSED, response
def _maintain_realm_list(self): """ Maintain realmlist by removing realms not updated for a while. """ with self.locks["realms"]: to_remove = [] for realm in self.realms: update_delay = time.time() - self.realms[realm]["last_update"] if update_delay > self.REALM_MAX_UPDATE_TIME: to_remove.append(realm) LOG.debug("Realm " + realm + " down, removed from list.") for realm_to_remove in to_remove: del self.realms[realm_to_remove]
def start(self): LOG.info("Starting login server") self._start_listen() simple_thread(self._accept_realms) self._accept_clients() self.shutdown_flag.set() self._stop_listen() AccountSessionManager.delete_all_sessions() LOG.info("Login server stopped.")
def _generate_local_proof(self): account_name = self.conn.account.name session = AccountSessionManager.get_session(account_name) if session is None: LOG.warning("Reconnection proof: account wasn't logged in!") return challenge = self.conn.recon_challenge to_hash = (account_name.encode("ascii") + self.proof_data + challenge + session.session_key_as_bytes) self.local_proof = sha1(to_hash)
def _try_recv_packet(self): packet, has_timeout = None, False try: packet = self._recv_packet() if packet is None: LOG.debug("Client closed the connection.") except socket.timeout: has_timeout = True return packet, has_timeout
def _get_more_data(self): some_data = None try: some_data = self.socket.recv(1024) except ConnectionError as exc: LOG.warning("WorldPacketReceiver: ConnectionError: " + str(exc)) traceback.print_tb(exc.__traceback__) if not some_data: raise WorldPacketReceiverException() self.packet_buf += some_data
def _recv_packet(self): # This assumes that all packets are received in no more or less than one # piece, which is a wrong. However, exceptions shouldn't occur often # with how short login messages are. try: data = self.socket.recv(1024) if data and DEBUG: print(get_data_dump(data), end="") return data or None except ConnectionError: LOG.info("Lost connection.") return None
def _process_reconnection(self): session = AccountSessionManager.get_session(self.account_name) if session is not None: LOG.debug("Reconnection: account was logged in.") self.conn.account = ReconChallenge._get_session_account(session) self.conn.recon_challenge = os.urandom(16) response = self._get_success_response() return LoginConnectionState.RECON_CHALL, response else: LOG.warning("Reconnection: account wasn't logged in!") response = self._get_failure_response() return LoginConnectionState.CLOSED, response
def delete_char(guid): """ Try to delete character and all associated data from the database. Return 0 on success, 1 on error. """ with DB.atomic() as transaction: try: _CharacterDestructor._delete_char(guid) except PeeweeException as exc: LOG.error("An error occured while deleting character:") LOG.error(str(exc)) transaction.rollback() return 1 return 0
def process(self): self._parse_packet(self.packet) LOG.debug("NameQuery: GUID {:X}".format(self.guid)) object_manager = self.conn.server.object_manager unit = object_manager.get_player(self.guid) if unit is None: LOG.warning("NameQueryHandler: couldn't find player {:X}".format( self.guid)) return None, None response = self._get_response_packet(unit) return None, response